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 */
24 char *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 
58 int WvTask::taskcount, WvTask::numtasks, WvTask::numrunning;
59 
60 WvTaskMan *WvTaskMan::singleton;
61 int WvTaskMan::links, WvTaskMan::magic_number;
62 WvTaskList WvTaskMan::all_tasks, WvTaskMan::free_tasks;
63 ucontext_t WvTaskMan::stackmaster_task, WvTaskMan::get_stack_return,
64  WvTaskMan::toplevel;
65 WvTask *WvTaskMan::current_task, *WvTaskMan::stack_target;
66 char *WvTaskMan::stacktop;
67 
68 static int context_return;
69 
70 
71 static bool use_shared_stack()
72 {
73  return RUNNING_ON_VALGRIND;
74 }
75 
76 
77 static 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 
88 WvTask::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 
106 WvTask::~WvTask()
107 {
108  numtasks--;
109  if (running)
110  numrunning--;
111  magic_number = 42;
112 }
113 
114 
115 void 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 
126 void 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 
147 void WvTaskMan::unlink()
148 {
149  links--;
150  if (!links)
151  {
152  delete singleton;
153  singleton = NULL;
154  }
155 }
156 
157 
158 static inline const char *Yes_No(bool val)
159 {
160  return val? "Yes": "No";
161 }
162 
163 
164 WvString 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 
186 WvTaskMan::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 
212 WvTaskMan::~WvTaskMan()
213 {
214  magic_number = -42;
215  free_tasks.zap();
216 }
217 
218 
219 WvTask *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 
246 int 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 
290 int 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 
341 void 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 
389 void 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 
398 void 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 = 1024; // 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 
458 void 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 
469 void 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 
540 const 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 
554 size_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:93
Represents a single thread of control.
Definition: wvtask.h:34
const char * cstr() const
return a (const char *) for this string.
Definition: wvstring.h:267
static WvTaskMan * get()
get/dereference the singleton global WvTaskMan
Definition: wvtask.cc:138
This is a WvList of WvStrings, and is a really handy way to parse strings.
Definition: wvstringlist.h:27
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:329
Provides co-operative multitasking support among WvTask instances.
Definition: wvtask.h:80