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