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