xref: /unit/src/nxt_fiber.c (revision 0:a63ceefd6ab0)
1 
2 /*
3  * Copyright (C) Igor Sysoev
4  * Copyright (C) NGINX, Inc.
5  */
6 
7 #include <nxt_main.h>
8 
9 
10 static char *nxt_fiber_create_stack(nxt_fiber_t *fib);
11 static void nxt_fiber_switch_stack(nxt_fiber_t *fib, jmp_buf *parent);
12 static void nxt_fiber_switch_handler(nxt_thread_t *thr, void *obj,
13     void *data);
14 static void nxt_fiber_switch(nxt_thread_t *thr, nxt_fiber_t *fib);
15 static void nxt_fiber_timer_handler(nxt_thread_t *thr, void *obj,
16     void *data);
17 
18 
19 #define                                                                       \
20 nxt_fiber_enqueue(thr, fib)                                                   \
21     nxt_thread_work_queue_add(thr, &(thr)->work_queue.main,                   \
22                               nxt_fiber_switch_handler, fib, NULL, thr->log)
23 
24 
25 nxt_fiber_main_t *
26 nxt_fiber_main_create(nxt_event_engine_t *engine)
27 {
28     nxt_fiber_main_t  *fm;
29 
30     fm = nxt_zalloc(sizeof(nxt_fiber_main_t));
31     if (nxt_slow_path(fm == NULL)) {
32         return NULL;
33     }
34 
35     fm->stack_size = 512 * 1024 - nxt_pagesize;
36     fm->idle = NULL;
37 
38     return fm;
39 }
40 
41 
42 nxt_int_t
43 nxt_fiber_create(nxt_fiber_start_t start, void *data, size_t stack)
44 {
45     int                  ret;
46     jmp_buf              parent;
47     nxt_fid_t            fid;
48     nxt_fiber_t          *fib;
49     nxt_thread_t         *thr;
50     nxt_fiber_main_t     *fm;
51 
52     thr = nxt_thread();
53     fm = thr->engine->fibers;
54 
55     fid = ++fm->fid;
56 
57     if (fid == 0) {
58         fid = ++fm->fid;
59     }
60 
61     fib = fm->idle;
62 
63     if (fib != NULL) {
64         fm->idle = fib->next;
65         fib->fid = fid;
66         fib->start = start;
67         fib->data = data;
68         fib->main = fm;
69 
70         nxt_log_debug(thr->log, "fiber create cached: %PF", fib->fid);
71         nxt_fiber_enqueue(thr, fib);
72         return NXT_OK;
73     }
74 
75     nxt_log_debug(thr->log, "fiber create");
76 
77     fib = nxt_malloc(sizeof(nxt_fiber_t));
78     if (nxt_slow_path(fib == NULL)) {
79         return NXT_ERROR;
80     }
81 
82     fib->fid = fid;
83     fib->start = start;
84     fib->data = data;
85     fib->stack_size = fm->stack_size;
86     fib->main = fm;
87 
88     fib->stack = nxt_fiber_create_stack(fib);
89 
90     if (nxt_fast_path(fib->stack != NULL)) {
91 
92         if (_setjmp(parent) != 0) {
93             nxt_log_debug(thr->log, "fiber create: %PF", fib->fid);
94             return NXT_OK;
95         }
96 
97         nxt_fiber_switch_stack(fib, &parent);
98         /* It does not return if the switch was successful. */
99     }
100 
101     ret = munmap(fib->stack - nxt_pagesize, fib->stack_size + nxt_pagesize);
102 
103     if (nxt_slow_path(ret != 0)) {
104         nxt_log_alert(thr->log, "munmap() failed %E", nxt_errno);
105     }
106 
107     nxt_free(fib);
108 
109     return NXT_ERROR;
110 }
111 
112 
113 #if (NXT_LINUX)
114 
115 static char *
116 nxt_fiber_create_stack(nxt_fiber_t *fib)
117 {
118     char    *s;
119     size_t  size;
120 
121     size = fib->stack_size + nxt_pagesize;
122 
123     s = mmap(NULL, size, PROT_READ | PROT_WRITE,
124              MAP_PRIVATE | MAP_ANON | MAP_GROWSDOWN, -1, 0);
125 
126     if (nxt_slow_path(s == MAP_FAILED)) {
127         nxt_thread_log_alert("fiber stack "
128                    "mmap(%uz, MAP_PRIVATE|MAP_ANON|MAP_GROWSDOWN) failed %E",
129                    size, nxt_errno);
130         return NULL;
131     }
132 
133     if (nxt_slow_path(mprotect(s, nxt_pagesize, PROT_NONE) != 0)) {
134         nxt_thread_log_alert("fiber stack mprotect(%uz, PROT_NONE) failed %E",
135                              size, nxt_errno);
136         return NULL;
137     }
138 
139     s += nxt_pagesize;
140 
141     nxt_thread_log_debug("fiber stack mmap: %p", s);
142 
143     return s;
144 }
145 
146 #else /* Generic version. */
147 
148 static char *
149 nxt_fiber_create_stack(nxt_fiber_t *fib)
150 {
151     char    *s;
152     size_t   size;
153 
154     size = fib->stack_size + nxt_pagesize;
155 
156     s = mmap(NULL, size, PROT_READ | PROT_WRITE, MAP_PRIVATE | MAP_ANON, -1, 0);
157 
158     if (nxt_slow_path(s == MAP_FAILED)) {
159         nxt_thread_log_alert("fiber stack "
160                              "mmap(%uz, MAP_PRIVATE|MAP_ANON) failed %E",
161                              size, nxt_errno);
162         return NULL;
163     }
164 
165     if (nxt_slow_path(mprotect(s, nxt_pagesize, PROT_NONE) != 0)) {
166         nxt_thread_log_alert("fiber stack mprotect(%uz, PROT_NONE) failed %E",
167                               size, nxt_errno);
168         return NULL;
169     }
170 
171     s += nxt_pagesize;
172 
173     nxt_thread_log_debug("fiber stack mmap: %p", s);
174 
175     return s;
176 }
177 
178 #endif
179 
180 
181 #if (NXT_LINUX && NXT_64BIT)
182 
183 /*
184  * Linux 64-bit ucontext version.  64-bit glibc makecontext() passes
185  * pointers as signed int's. The bug has been fixed in glibc 2.8.
186  */
187 
188 static void nxt_fiber_trampoline(uint32_t fh, uint32_t fl, uint32_t ph,
189     uint32_t pl);
190 
191 
192 static void
193 nxt_fiber_switch_stack(nxt_fiber_t *fib, jmp_buf *parent)
194 {
195     ucontext_t  uc;
196 
197     nxt_thread_log_debug("fiber switch to stack: %p", fib->stack);
198 
199     if (nxt_slow_path(getcontext(&uc) != 0)) {
200         nxt_thread_log_alert("getcontext() failed");
201         return;
202     }
203 
204     uc.uc_link = NULL;
205     uc.uc_stack.ss_sp = fib->stack;
206     uc.uc_stack.ss_size = fib->stack_size;
207 
208     makecontext(&uc, (void (*)(void)) nxt_fiber_trampoline, 4,
209                 (uint32_t) ((uintptr_t) fib >> 32),
210                 (uint32_t) ((uintptr_t) fib & 0xffffffff),
211                 (uint32_t) ((uintptr_t) parent >> 32),
212                 (uint32_t) ((uintptr_t) parent & 0xffffffff));
213 
214     setcontext(&uc);
215 
216     nxt_thread_log_alert("setcontext() failed");
217 }
218 
219 
220 static void
221 nxt_fiber_trampoline(uint32_t fh, uint32_t fl, uint32_t ph, uint32_t pl)
222 {
223     jmp_buf       *parent;
224     nxt_fiber_t   *fib;
225     nxt_thread_t  *thr;
226 
227     fib = (nxt_fiber_t *) (((uintptr_t) fh << 32) + fl);
228     parent = (jmp_buf *) (((uintptr_t) ph << 32) + pl);
229 
230     thr = nxt_thread();
231 
232     if (_setjmp(fib->jmp) == 0) {
233         nxt_log_debug(thr->log, "fiber return to parent stack");
234 
235         nxt_fiber_enqueue(thr, fib);
236         _longjmp(*parent, 1);
237         nxt_unreachable();
238     }
239 
240     nxt_log_debug(thr->log, "fiber start");
241 
242     fib->start(fib->data);
243 
244     nxt_fiber_exit(&fib->main->fiber, NULL);
245     nxt_unreachable();
246 }
247 
248 #elif (NXT_HAVE_UCONTEXT)
249 
250 /* Generic ucontext version. */
251 
252 static void nxt_fiber_trampoline(nxt_fiber_t *fib, jmp_buf *parent);
253 
254 
255 static void
256 nxt_fiber_switch_stack(nxt_fiber_t *fib, jmp_buf *parent)
257 {
258     ucontext_t  uc;
259 
260     nxt_thread_log_debug("fiber switch to stack: %p", fib->stack);
261 
262     if (nxt_slow_path(getcontext(&uc) != 0)) {
263         nxt_thread_log_alert("getcontext() failed");
264         return;
265     }
266 
267     uc.uc_link = NULL;
268     uc.uc_stack.ss_sp = fib->stack;
269     uc.uc_stack.ss_size = fib->stack_size;
270 
271     makecontext(&uc, (void (*)(void)) nxt_fiber_trampoline, 2, fib, parent);
272 
273     setcontext(&uc);
274 
275 #if !(NXT_SOLARIS)
276     /* Solaris declares setcontext() as __NORETURN. */
277 
278     nxt_thread_log_alert("setcontext() failed");
279 #endif
280 }
281 
282 
283 static void
284 nxt_fiber_trampoline(nxt_fiber_t *fib, jmp_buf *parent)
285 {
286     nxt_thread_t  *thr;
287 
288     thr = nxt_thread();
289 
290     if (_setjmp(fib->jmp) == 0) {
291         nxt_log_debug(thr->log, "fiber return to parent stack");
292 
293         nxt_fiber_enqueue(thr, fib);
294         _longjmp(*parent, 1);
295         nxt_unreachable();
296     }
297 
298     nxt_log_debug(thr->log, "fiber start");
299 
300     fib->start(fib->data);
301 
302     nxt_fiber_exit(&fib->main->fiber, NULL);
303     nxt_unreachable();
304 }
305 
306 #else
307 
308 #error No ucontext(3) interface.
309 
310 #endif
311 
312 
313 static void
314 nxt_fiber_switch_handler(nxt_thread_t *thr, void *obj, void *data)
315 {
316     nxt_fiber_t  *fib;
317 
318     fib = obj;
319 
320     nxt_fiber_switch(thr, fib);
321     nxt_unreachable();
322 }
323 
324 
325 static void
326 nxt_fiber_switch(nxt_thread_t *thr, nxt_fiber_t *fib)
327 {
328     nxt_log_debug(thr->log, "fiber switch: %PF", fib->fid);
329 
330     thr->fiber = fib;
331     _longjmp(fib->jmp, 1);
332     nxt_unreachable();
333 }
334 
335 
336 nxt_fiber_t *
337 nxt_fiber_self(nxt_thread_t *thr)
338 {
339     return (nxt_fast_path(thr != NULL)) ? thr->fiber : NULL;
340 }
341 
342 
343 void
344 nxt_fiber_yield(void)
345 {
346     nxt_fiber_t   *fib;
347     nxt_thread_t  *thr;
348 
349     thr = nxt_thread();
350     fib = thr->fiber;
351 
352     if (_setjmp(fib->jmp) == 0) {
353 
354         nxt_log_debug(thr->log, "fiber yield");
355 
356         nxt_fiber_enqueue(thr, fib);
357         nxt_fiber_switch(thr, &fib->main->fiber);
358         nxt_unreachable();
359     }
360 
361     nxt_log_debug(thr->log, "fiber yield return");
362 }
363 
364 
365 void
366 nxt_fiber_sleep(nxt_msec_t timeout)
367 {
368     nxt_fiber_t   *fib;
369     nxt_thread_t  *thr;
370 
371     thr = nxt_thread();
372     fib = thr->fiber;
373 
374     fib->timer.work_queue = &thr->work_queue.main;
375     fib->timer.handler = nxt_fiber_timer_handler;
376     fib->timer.log = &nxt_main_log;
377 
378     nxt_event_timer_add(thr->engine, &fib->timer, timeout);
379 
380     if (_setjmp(fib->jmp) == 0) {
381 
382         nxt_log_debug(thr->log, "fiber sleep: %T", timeout);
383 
384         nxt_fiber_switch(thr, &fib->main->fiber);
385         nxt_unreachable();
386     }
387 
388     nxt_log_debug(thr->log, "fiber sleep return");
389 }
390 
391 
392 static void
393 nxt_fiber_timer_handler(nxt_thread_t *thr, void *obj, void *data)
394 {
395     nxt_fiber_t        *fib;
396     nxt_event_timer_t  *ev;
397 
398     ev = obj;
399 
400     nxt_log_debug(thr->log, "fiber timer handler");
401 
402     fib = nxt_event_timer_data(ev, nxt_fiber_t, timer);
403 
404     nxt_fiber_switch(thr, fib);
405     nxt_unreachable();
406 }
407 
408 
409 void
410 nxt_fiber_wait(void)
411 {
412     nxt_fiber_t   *fib;
413     nxt_thread_t  *thr;
414 
415     thr = nxt_thread();
416     fib = thr->fiber;
417 
418     if (_setjmp(fib->jmp) == 0) {
419         nxt_log_debug(thr->log, "fiber wait");
420 
421         nxt_fiber_switch(thr, &fib->main->fiber);
422         nxt_unreachable();
423     }
424 
425     nxt_log_debug(thr->log, "fiber wait return");
426 }
427 
428 
429 void
430 nxt_fiber_exit(nxt_fiber_t *next, void *data)
431 {
432     nxt_fiber_t   *fib;
433     nxt_thread_t  *thr;
434 
435     thr = nxt_thread();
436     fib = thr->fiber;
437 
438     nxt_log_debug(thr->log, "fiber exit");
439 
440     /* TODO: limit idle fibers. */
441     fib->next = fib->main->idle;
442     fib->main->idle = fib;
443 
444     nxt_fiber_switch(thr, next);
445     nxt_unreachable();
446 }
447