WvStreams
wvtask.cc
1/*
2 * Worldvisions Weaver Software:
3 * Copyright (C) 1997-2002 Net Integration Technologies, Inc.
4 *
5 * A set of classes that provide co-operative multitasking support. See
6 * wvtask.h for more information.
7 */
8
9#include "wvautoconf.h"
10#ifdef __GNUC__
11# define alloca __builtin_alloca
12#else
13# ifdef _MSC_VER
14# include <malloc.h>
15# define alloca _alloca
16# else
17# if HAVE_ALLOCA_H
18# include <alloca.h>
19# else
20# ifdef _AIX
21#pragma alloca
22# else
23# ifndef alloca /* predefined by HP cc +Olibcalls */
24char *alloca ();
25# endif
26# endif
27# endif
28# endif
29#endif
30
31#include "wvtask.h"
32#include <stdio.h>
33#include <stdlib.h>
34#include <assert.h>
35#include <sys/mman.h>
36#include <signal.h>
37#include <unistd.h>
38#include <sys/resource.h>
39
40#ifdef HAVE_VALGRIND_MEMCHECK_H
41#include <valgrind/memcheck.h>
42// Compatibility for Valgrind 3.1 and previous
43#ifndef VALGRIND_MAKE_MEM_DEFINED
44#define VALGRIND_MAKE_MEM_DEFINED VALGRIND_MAKE_READABLE
45#endif
46#else
47#define VALGRIND_MAKE_MEM_DEFINED(x, y)
48#define RUNNING_ON_VALGRIND 0
49#endif
50
51#define TASK_DEBUG 0
52#if TASK_DEBUG
53# define Dprintf(fmt, args...) fprintf(stderr, fmt, ##args)
54#else
55# define Dprintf(fmt, args...)
56#endif
57
58int WvTask::taskcount, WvTask::numtasks, WvTask::numrunning;
59
60WvTaskMan *WvTaskMan::singleton;
61int WvTaskMan::links;
62int volatile WvTaskMan::magic_number;
63WvTaskList WvTaskMan::all_tasks, WvTaskMan::free_tasks;
64ucontext_t WvTaskMan::stackmaster_task, WvTaskMan::get_stack_return,
65 WvTaskMan::toplevel;
66WvTask *WvTaskMan::current_task, *WvTaskMan::stack_target;
67char *WvTaskMan::stacktop;
68
69static int context_return;
70
71
72static bool use_shared_stack()
73{
74 return RUNNING_ON_VALGRIND;
75}
76
77
78static void valgrind_fix(char *stacktop)
79{
80#ifdef HAVE_VALGRIND_MEMCHECK_H
81 char val;
82 //printf("valgrind fix: %p-%p\n", &val, stacktop);
83 assert(stacktop > &val);
84#endif
85 VALGRIND_MAKE_MEM_DEFINED(&val, stacktop - &val);
86}
87
88
89WvTask::WvTask(WvTaskMan &_man, size_t _stacksize) : man(_man)
90{
91 stacksize = _stacksize;
92 running = recycled = false;
93 func = NULL;
94 userdata = NULL;
95
96 tid = ++taskcount;
97 numtasks++;
98 magic_number = WVTASK_MAGIC;
99 stack_magic = NULL;
100
101 man.get_stack(*this, stacksize);
102
103 man.all_tasks.append(this, false);
104}
105
106
107WvTask::~WvTask()
108{
109 numtasks--;
110 if (running)
111 numrunning--;
112 magic_number = 42;
113}
114
115
116void WvTask::start(WvStringParm _name, TaskFunc *_func, void *_userdata)
117{
118 assert(!recycled);
119 name = _name;
120 func = _func;
121 userdata = _userdata;
122 running = true;
123 numrunning++;
124}
125
126
127void WvTask::recycle()
128{
129 assert(!running);
130
131 if (!running && !recycled)
132 {
133 man.free_tasks.append(this, true);
134 recycled = true;
135 }
136}
137
138
140{
141 if (!links)
142 singleton = new WvTaskMan;
143 links++;
144 return singleton;
145}
146
147
148void WvTaskMan::unlink()
149{
150 links--;
151 if (!links)
152 {
153 delete singleton;
154 singleton = NULL;
155 }
156}
157
158
159static inline const char *Yes_No(bool val)
160{
161 return val? "Yes": "No";
162}
163
164
165WvString WvTaskMan::debugger_tasks_run_cb(WvStringParm cmd, WvStringList &args,
166 WvStreamsDebugger::ResultCallback result_cb, void *)
167{
168 const char *format_str = "%5s%s%7s%s%8s%s%6s%s%s";
169 WvStringList result;
170 result.append(format_str, "--TID", "-", "Running", "-", "Recycled", "-", "-StkSz", "-", "Name-----");
171 result_cb(cmd, result);
172 WvTaskList::Iter i(all_tasks);
173 for (i.rewind(); i.next(); )
174 {
175 result.zap();
176 result.append(format_str, i->tid, " ",
177 Yes_No(i->running), " ",
178 Yes_No(i->recycled), " ",
179 i->stacksize, " ",
180 i->name);
181 result_cb(cmd, result);
182 }
183 return WvString::null;
184}
185
186
187WvTaskMan::WvTaskMan()
188{
189 static bool first = true;
190 if (first)
191 {
192 first = false;
193 WvStreamsDebugger::add_command("tasks", 0, debugger_tasks_run_cb, 0);
194 }
195
196 stack_target = NULL;
197 current_task = NULL;
198 magic_number = -WVTASK_MAGIC;
199
200 stacktop = (char *)alloca(0);
201
202 context_return = 0;
203 assert(getcontext(&get_stack_return) == 0);
204 if (context_return == 0)
205 {
206 // initial setup - start the stackmaster() task (never returns!)
207 stackmaster();
208 }
209 // if we get here, stackmaster did a longjmp back to us.
210}
211
212
213WvTaskMan::~WvTaskMan()
214{
215 magic_number = -42;
216 free_tasks.zap();
217}
218
219
220WvTask *WvTaskMan::start(WvStringParm name,
221 WvTask::TaskFunc *func, void *userdata,
222 size_t stacksize)
223{
224 WvTask *t;
225
226 WvTaskList::Iter i(free_tasks);
227 for (i.rewind(); i.next(); )
228 {
229 if (i().stacksize >= stacksize)
230 {
231 t = &i();
232 i.set_autofree(false);
233 i.unlink();
234 t->recycled = false;
235 t->start(name, func, userdata);
236 return t;
237 }
238 }
239
240 // if we get here, no matching task was found.
241 t = new WvTask(*this, stacksize);
242 t->start(name, func, userdata);
243 return t;
244}
245
246
247int WvTaskMan::run(WvTask &task, int val)
248{
249 assert(magic_number == -WVTASK_MAGIC);
250 assert(task.magic_number == WVTASK_MAGIC);
251 assert(!task.recycled);
252
253 Dprintf("WvTaskMan: running task #%d with value %d (%s)\n",
254 task.tid, val, (const char *)task.name);
255
256 if (&task == current_task)
257 return val; // that's easy!
258
259 WvTask *old_task = current_task;
260 current_task = &task;
261 ucontext_t *state;
262
263 if (!old_task)
264 state = &toplevel; // top-level call (not in an actual task yet)
265 else
266 state = &old_task->mystate;
267
268 context_return = 0;
269 assert(getcontext(state) == 0);
270 int newval = context_return;
271 if (newval == 0)
272 {
273 // saved the state, now run the task.
274 context_return = val;
275 setcontext(&task.mystate);
276 return -1;
277 }
278 else
279 {
280 // need to make state readable to see if we need to make more readable..
281 VALGRIND_MAKE_MEM_DEFINED(&state, sizeof(state));
282 // someone did yield() (if toplevel) or run() on our old task; done.
283 if (state != &toplevel)
284 valgrind_fix(stacktop);
285 current_task = old_task;
286 return newval;
287 }
288}
289
290
291int WvTaskMan::yield(int val)
292{
293 if (!current_task)
294 return 0; // weird...
295
296 Dprintf("WvTaskMan: yielding from task #%d with value %d (%s)\n",
297 current_task->tid, val, (const char *)current_task->name);
298
299 assert(current_task->stack_magic);
300
301 // if this fails, this task overflowed its stack. Make it bigger!
302 VALGRIND_MAKE_MEM_DEFINED(current_task->stack_magic,
303 sizeof(current_task->stack_magic));
304 assert(*current_task->stack_magic == WVTASK_MAGIC);
305
306#if TASK_DEBUG
307 if (use_shared_stack())
308 {
309 size_t stackleft;
310 char *stackbottom = (char *)(current_task->stack_magic + 1);
311 for (stackleft = 0; stackleft < current_task->stacksize; stackleft++)
312 {
313 if (stackbottom[stackleft] != 0x42)
314 break;
315 }
316 Dprintf("WvTaskMan: remaining stack after #%d (%s): %ld/%ld\n",
317 current_task->tid, current_task->name.cstr(), (long)stackleft,
318 (long)current_task->stacksize);
319 }
320#endif
321
322 context_return = 0;
323 assert(getcontext(&current_task->mystate) == 0);
324 int newval = context_return;
325 if (newval == 0)
326 {
327 // saved the task state; now yield to the toplevel.
328 context_return = val;
329 setcontext(&toplevel);
330 return -1;
331 }
332 else
333 {
334 // back via longjmp, because someone called run() again. Let's go
335 // back to our running task...
336 valgrind_fix(stacktop);
337 return newval;
338 }
339}
340
341
342void WvTaskMan::get_stack(WvTask &task, size_t size)
343{
344 context_return = 0;
345 assert(getcontext(&get_stack_return) == 0);
346 if (context_return == 0)
347 {
348 assert(magic_number == -WVTASK_MAGIC);
349 assert(task.magic_number == WVTASK_MAGIC);
350
351 if (!use_shared_stack())
352 {
353#if defined(__linux__) && (defined(__386__) || defined(__i386) || defined(__i386__))
354 static char *next_stack_addr = (char *)0xB0000000;
355 static const size_t stack_shift = 0x00100000;
356
357 next_stack_addr -= stack_shift;
358#else
359 static char *next_stack_addr = NULL;
360#endif
361
362 task.stack = mmap(next_stack_addr, task.stacksize,
363 PROT_READ | PROT_WRITE,
364#ifndef MACOS
365 MAP_PRIVATE | MAP_ANONYMOUS,
366#else
367 MAP_PRIVATE,
368#endif
369 -1, 0);
370 }
371
372 // initial setup
373 stack_target = &task;
374 context_return = size/1024 + (size%1024 > 0);
375 setcontext(&stackmaster_task);
376 }
377 else
378 {
379 if (current_task)
380 valgrind_fix(stacktop);
381 assert(magic_number == -WVTASK_MAGIC);
382 assert(task.magic_number == WVTASK_MAGIC);
383
384 // back from stackmaster - the task is now set up.
385 return;
386 }
387}
388
389
390void WvTaskMan::stackmaster()
391{
392 // leave lots of room on the "main" stack before doing our magic
393 alloca(1024*1024);
394
395 _stackmaster();
396}
397
398
399void WvTaskMan::_stackmaster()
400{
401 int val;
402 size_t total;
403
404 Dprintf("stackmaster 1\n");
405
406 // the main loop runs once from the constructor, and then once more
407 // after each stack allocation.
408 for (;;)
409 {
410 assert(magic_number == -WVTASK_MAGIC);
411
412 context_return = 0;
413 assert(getcontext(&stackmaster_task) == 0);
414 val = context_return;
415 if (val == 0)
416 {
417 assert(magic_number == -WVTASK_MAGIC);
418
419 // just did setjmp; save stackmaster's current state (with
420 // all current stack allocations) and go back to get_stack
421 // (or the constructor, if that's what called us)
422 context_return = 1;
423 setcontext(&get_stack_return);
424 }
425 else
426 {
427 valgrind_fix(stacktop);
428 assert(magic_number == -WVTASK_MAGIC);
429
430 total = (val+1) * (size_t)1024;
431
432 if (!use_shared_stack())
433 total = 1024; // enough to save the do_task stack frame
434
435 // set up a stack frame for the new task. This runs once
436 // per get_stack.
437 //alloc_stack_and_switch(total);
438 do_task();
439
440 assert(magic_number == -WVTASK_MAGIC);
441
442 // allocate the stack area so we never use it again
443 alloca(total);
444
445 // a little sentinel so we can detect stack overflows
446 stack_target->stack_magic = (int *)alloca(sizeof(int));
447 *stack_target->stack_magic = WVTASK_MAGIC;
448
449 // clear the stack to 0x42 so we can count unused stack
450 // space later.
451#if TASK_DEBUG
452 memset(stack_target->stack_magic + 1, 0x42, total - 1024);
453#endif
454 }
455 }
456}
457
458
459void WvTaskMan::call_func(WvTask *task)
460{
461 Dprintf("WvTaskMan: calling task #%d (%s)\n",
462 task->tid, (const char *)task->name);
463 task->func(task->userdata);
464 Dprintf("WvTaskMan: returning from task #%d (%s)\n",
465 task->tid, (const char *)task->name);
466 context_return = 1;
467}
468
469
470void WvTaskMan::do_task()
471{
472 assert(magic_number == -WVTASK_MAGIC);
473 WvTask *task = stack_target;
474 assert(task->magic_number == WVTASK_MAGIC);
475
476 // back here from longjmp; someone wants stack space.
477 context_return = 0;
478 assert(getcontext(&task->mystate) == 0);
479 if (context_return == 0)
480 {
481 // done the setjmp; that means the target task now has
482 // a working jmp_buf all set up. Leave space on the stack
483 // for his data, then repeat the loop in _stackmaster (so we can
484 // return to get_stack(), and allocate more stack for someone later)
485 //
486 // Note that nothing on the allocated stack needs to be valid; when
487 // they longjmp to task->mystate, they'll have a new stack pointer
488 // and they'll already know what to do (in the 'else' clause, below)
489 Dprintf("stackmaster 5\n");
490 return;
491 }
492 else
493 {
494 // someone did a run() on the task, which
495 // means they're ready to make it go. Do it.
496 valgrind_fix(stacktop);
497 for (;;)
498 {
499 assert(magic_number == -WVTASK_MAGIC);
500 assert(task);
501 assert(task->magic_number == WVTASK_MAGIC);
502
503 if (task->func && task->running)
504 {
505 if (use_shared_stack())
506 {
507 // this is the task's main function. It can call yield()
508 // to give up its timeslice if it wants. Either way, it
509 // only returns to *us* if the function actually finishes.
510 task->func(task->userdata);
511 }
512 else
513 {
514 assert(getcontext(&task->func_call) == 0);
515 task->func_call.uc_stack.ss_size = task->stacksize;
516 task->func_call.uc_stack.ss_sp = task->stack;
517 task->func_call.uc_stack.ss_flags = 0;
518 task->func_call.uc_link = &task->func_return;
519 Dprintf("WvTaskMan: makecontext #%d (%s)\n",
520 task->tid, (const char *)task->name);
521 makecontext(&task->func_call,
522 (void (*)(void))call_func, 1, task);
523
524 context_return = 0;
525 assert(getcontext(&task->func_return) == 0);
526 if (context_return == 0)
527 setcontext(&task->func_call);
528 }
529
530 // the task's function terminated.
531 task->name = "DEAD";
532 task->running = false;
533 task->numrunning--;
534 }
535 yield();
536 }
537 }
538}
539
540
541const void *WvTaskMan::current_top_of_stack()
542{
543#ifdef HAVE_LIBC_STACK_END
544 extern const void *__libc_stack_end;
545 if (use_shared_stack() || current_task == NULL)
546 return __libc_stack_end;
547 else
548 return (const char *)current_task->stack + current_task->stacksize;
549#else
550 return 0;
551#endif
552}
553
554
555size_t WvTaskMan::current_stacksize_limit()
556{
557 if (use_shared_stack() || current_task == NULL)
558 {
559 struct rlimit rl;
560 if (getrlimit(RLIMIT_STACK, &rl) == 0)
561 return size_t(rl.rlim_cur);
562 else
563 return 0;
564 }
565 else
566 return size_t(current_task->stacksize);
567}
568
569
A WvFastString acts exactly like a WvString, but can take (const char *) strings without needing to a...
Definition wvstring.h:94
const char * cstr() const
return a (const char *) for this string.
Definition wvstring.h:267
This is a WvList of WvStrings, and is a really handy way to parse strings.
WvString is an implementation of a simple and efficient printable-string class.
Definition wvstring.h:330
Provides co-operative multitasking support among WvTask instances.
Definition wvtask.h:82
static WvTaskMan * get()
get/dereference the singleton global WvTaskMan
Definition wvtask.cc:139
Represents a single thread of control.
Definition wvtask.h:35