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