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