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;
62 int volatile WvTaskMan::magic_number;
63 WvTaskList WvTaskMan::all_tasks, WvTaskMan::free_tasks;
64 ucontext_t WvTaskMan::stackmaster_task, WvTaskMan::get_stack_return,
65  WvTaskMan::toplevel;
66 WvTask *WvTaskMan::current_task, *WvTaskMan::stack_target;
67 char *WvTaskMan::stacktop;
68 
69 static int context_return;
70 
71 
72 static bool use_shared_stack()
73 {
74  return RUNNING_ON_VALGRIND;
75 }
76 
77 
78 static 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 
89 WvTask::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 
107 WvTask::~WvTask()
108 {
109  numtasks--;
110  if (running)
111  numrunning--;
112  magic_number = 42;
113 }
114 
115 
116 void 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 
127 void 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 
148 void WvTaskMan::unlink()
149 {
150  links--;
151  if (!links)
152  {
153  delete singleton;
154  singleton = NULL;
155  }
156 }
157 
158 
159 static inline const char *Yes_No(bool val)
160 {
161  return val? "Yes": "No";
162 }
163 
164 
165 WvString 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 
187 WvTaskMan::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 
213 WvTaskMan::~WvTaskMan()
214 {
215  magic_number = -42;
216  free_tasks.zap();
217 }
218 
219 
220 WvTask *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 
247 int 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 
291 int 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 
342 void 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 
390 void 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 
399 void 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 
459 void 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 
470 void 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 
541 const 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 
555 size_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 
WvTaskMan
Provides co-operative multitasking support among WvTask instances.
Definition: wvtask.h:81
WvTask
Represents a single thread of control.
Definition: wvtask.h:34
WvString
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:329
WvTaskMan::get
static WvTaskMan * get()
get/dereference the singleton global WvTaskMan
Definition: wvtask.cc:139
WvFastString::cstr
const char * cstr() const
return a (const char *) for this string.
Definition: wvstring.h:267
WvStringList
This is a WvList of WvStrings, and is a really handy way to parse strings.
Definition: wvstringlist.h:27