WvStreams
wvtask.cc
00001 /*
00002  * Worldvisions Weaver Software:
00003  *   Copyright (C) 1997-2002 Net Integration Technologies, Inc.
00004  * 
00005  * A set of classes that provide co-operative multitasking support.  See
00006  * wvtask.h for more information.
00007  */
00008 
00009 #include "wvautoconf.h"
00010 #ifdef __GNUC__
00011 # define alloca __builtin_alloca
00012 #else
00013 # ifdef _MSC_VER
00014 #  include <malloc.h>
00015 #  define alloca _alloca
00016 # else
00017 #  if HAVE_ALLOCA_H
00018 #   include <alloca.h>
00019 #  else
00020 #   ifdef _AIX
00021 #pragma alloca
00022 #   else
00023 #    ifndef alloca /* predefined by HP cc +Olibcalls */
00024 char *alloca ();
00025 #    endif
00026 #   endif
00027 #  endif
00028 # endif
00029 #endif
00030 
00031 #include "wvtask.h"
00032 #include <stdio.h>
00033 #include <stdlib.h>
00034 #include <assert.h>
00035 #include <sys/mman.h>
00036 #include <signal.h>
00037 #include <unistd.h>
00038 #include <sys/resource.h>
00039 
00040 #ifdef HAVE_VALGRIND_MEMCHECK_H
00041 #include <valgrind/memcheck.h>
00042 // Compatibility for Valgrind 3.1 and previous
00043 #ifndef VALGRIND_MAKE_MEM_DEFINED
00044 #define VALGRIND_MAKE_MEM_DEFINED VALGRIND_MAKE_READABLE 
00045 #endif
00046 #else
00047 #define VALGRIND_MAKE_MEM_DEFINED(x, y)
00048 #define RUNNING_ON_VALGRIND 0
00049 #endif
00050 
00051 #define TASK_DEBUG 0
00052 #if TASK_DEBUG
00053 # define Dprintf(fmt, args...) fprintf(stderr, fmt, ##args)
00054 #else
00055 # define Dprintf(fmt, args...)
00056 #endif
00057 
00058 int WvTask::taskcount, WvTask::numtasks, WvTask::numrunning;
00059 
00060 WvTaskMan *WvTaskMan::singleton;
00061 int WvTaskMan::links, WvTaskMan::magic_number;
00062 WvTaskList WvTaskMan::all_tasks, WvTaskMan::free_tasks;
00063 ucontext_t WvTaskMan::stackmaster_task, WvTaskMan::get_stack_return,
00064     WvTaskMan::toplevel;
00065 WvTask *WvTaskMan::current_task, *WvTaskMan::stack_target;
00066 char *WvTaskMan::stacktop;
00067 
00068 static int context_return;
00069 
00070 
00071 static bool use_shared_stack()
00072 {
00073     return RUNNING_ON_VALGRIND;
00074 }
00075 
00076 
00077 static void valgrind_fix(char *stacktop)
00078 {
00079 #ifdef HAVE_VALGRIND_MEMCHECK_H
00080     char val;
00081     //printf("valgrind fix: %p-%p\n", &val, stacktop);
00082     assert(stacktop > &val);
00083 #endif
00084     VALGRIND_MAKE_MEM_DEFINED(&val, stacktop - &val);
00085 }
00086 
00087 
00088 WvTask::WvTask(WvTaskMan &_man, size_t _stacksize) : man(_man)
00089 {
00090     stacksize = _stacksize;
00091     running = recycled = false;
00092     func = NULL;
00093     userdata = NULL;
00094     
00095     tid = ++taskcount;
00096     numtasks++;
00097     magic_number = WVTASK_MAGIC;
00098     stack_magic = NULL;
00099     
00100     man.get_stack(*this, stacksize);
00101 
00102     man.all_tasks.append(this, false);
00103 }
00104 
00105 
00106 WvTask::~WvTask()
00107 {
00108     numtasks--;
00109     if (running)
00110         numrunning--;
00111     magic_number = 42;
00112 }
00113 
00114 
00115 void WvTask::start(WvStringParm _name, TaskFunc *_func, void *_userdata)
00116 {
00117     assert(!recycled);
00118     name = _name;
00119     func = _func;
00120     userdata = _userdata;
00121     running = true;
00122     numrunning++;
00123 }
00124 
00125 
00126 void WvTask::recycle()
00127 {
00128     assert(!running);
00129     
00130     if (!running && !recycled)
00131     {
00132         man.free_tasks.append(this, true);
00133         recycled = true;
00134     }
00135 }
00136 
00137 
00138 WvTaskMan *WvTaskMan::get()
00139 {
00140     if (!links)
00141         singleton = new WvTaskMan;
00142     links++;
00143     return singleton;
00144 }
00145 
00146 
00147 void WvTaskMan::unlink()
00148 {
00149     links--;
00150     if (!links)
00151     {
00152         delete singleton;
00153         singleton = NULL;
00154     }
00155 }
00156 
00157 
00158 static inline const char *Yes_No(bool val)
00159 {
00160     return val? "Yes": "No";
00161 }
00162 
00163 
00164 WvString WvTaskMan::debugger_tasks_run_cb(WvStringParm cmd, WvStringList &args,
00165         WvStreamsDebugger::ResultCallback result_cb, void *)
00166 {
00167     const char *format_str = "%5s%s%7s%s%8s%s%6s%s%s";
00168     WvStringList result;
00169     result.append(format_str, "--TID", "-", "Running", "-", "Recycled", "-", "-StkSz", "-", "Name-----");
00170     result_cb(cmd, result);
00171     WvTaskList::Iter i(all_tasks);
00172     for (i.rewind(); i.next(); )
00173     {
00174         result.zap();
00175         result.append(format_str, i->tid, " ",
00176                 Yes_No(i->running), " ",
00177                 Yes_No(i->recycled), " ",
00178                 i->stacksize, " ",
00179                 i->name);
00180         result_cb(cmd, result);
00181     }
00182     return WvString::null;
00183 }
00184 
00185 
00186 WvTaskMan::WvTaskMan()
00187 {
00188     static bool first = true;
00189     if (first)
00190     {
00191         first = false;
00192         WvStreamsDebugger::add_command("tasks", 0, debugger_tasks_run_cb, 0);
00193     }
00194 
00195     stack_target = NULL;
00196     current_task = NULL;
00197     magic_number = -WVTASK_MAGIC;
00198     
00199     stacktop = (char *)alloca(0);
00200     
00201     context_return = 0;
00202     assert(getcontext(&get_stack_return) == 0);
00203     if (context_return == 0)
00204     {
00205         // initial setup - start the stackmaster() task (never returns!)
00206         stackmaster();
00207     }
00208     // if we get here, stackmaster did a longjmp back to us.
00209 }
00210 
00211 
00212 WvTaskMan::~WvTaskMan()
00213 {    
00214     magic_number = -42;
00215     free_tasks.zap();
00216 }
00217 
00218 
00219 WvTask *WvTaskMan::start(WvStringParm name, 
00220                          WvTask::TaskFunc *func, void *userdata,
00221                          size_t stacksize)
00222 {
00223     WvTask *t;
00224     
00225     WvTaskList::Iter i(free_tasks);
00226     for (i.rewind(); i.next(); )
00227     {
00228         if (i().stacksize >= stacksize)
00229         {
00230             t = &i();
00231             i.set_autofree(false);
00232             i.unlink();
00233             t->recycled = false;
00234             t->start(name, func, userdata);
00235             return t;
00236         }
00237     }
00238     
00239     // if we get here, no matching task was found.
00240     t = new WvTask(*this, stacksize);
00241     t->start(name, func, userdata);
00242     return t;
00243 }
00244 
00245 
00246 int WvTaskMan::run(WvTask &task, int val)
00247 {
00248     assert(magic_number == -WVTASK_MAGIC);
00249     assert(task.magic_number == WVTASK_MAGIC);
00250     assert(!task.recycled);
00251     
00252     Dprintf("WvTaskMan: running task #%d with value %d (%s)\n",
00253             task.tid, val, (const char *)task.name);
00254     
00255     if (&task == current_task)
00256         return val; // that's easy!
00257         
00258     WvTask *old_task = current_task;
00259     current_task = &task;
00260     ucontext_t *state;
00261     
00262     if (!old_task)
00263         state = &toplevel; // top-level call (not in an actual task yet)
00264     else
00265         state = &old_task->mystate;
00266     
00267     context_return = 0;
00268     assert(getcontext(state) == 0);
00269     int newval = context_return;
00270     if (newval == 0)
00271     {
00272         // saved the state, now run the task.
00273         context_return = val;
00274         setcontext(&task.mystate);
00275         return -1;
00276     }
00277     else
00278     {
00279         // need to make state readable to see if we need to make more readable..
00280         VALGRIND_MAKE_MEM_DEFINED(&state, sizeof(state));
00281         // someone did yield() (if toplevel) or run() on our old task; done.
00282         if (state != &toplevel)
00283             valgrind_fix(stacktop);
00284         current_task = old_task;
00285         return newval;
00286     }
00287 }
00288 
00289 
00290 int WvTaskMan::yield(int val)
00291 {
00292     if (!current_task)
00293         return 0; // weird...
00294     
00295     Dprintf("WvTaskMan: yielding from task #%d with value %d (%s)\n",
00296            current_task->tid, val, (const char *)current_task->name);
00297     
00298     assert(current_task->stack_magic);
00299     
00300     // if this fails, this task overflowed its stack.  Make it bigger!
00301     VALGRIND_MAKE_MEM_DEFINED(current_task->stack_magic,
00302                            sizeof(current_task->stack_magic));
00303     assert(*current_task->stack_magic == WVTASK_MAGIC);
00304 
00305 #if TASK_DEBUG
00306     if (use_shared_stack())
00307     {
00308         size_t stackleft;
00309         char *stackbottom = (char *)(current_task->stack_magic + 1);
00310         for (stackleft = 0; stackleft < current_task->stacksize; stackleft++)
00311         {
00312             if (stackbottom[stackleft] != 0x42)
00313                 break;
00314         }
00315         Dprintf("WvTaskMan: remaining stack after #%d (%s): %ld/%ld\n",
00316                 current_task->tid, current_task->name.cstr(), (long)stackleft,
00317                 (long)current_task->stacksize);
00318     }
00319 #endif
00320                 
00321     context_return = 0;
00322     assert(getcontext(&current_task->mystate) == 0);
00323     int newval = context_return;
00324     if (newval == 0)
00325     {
00326         // saved the task state; now yield to the toplevel.
00327         context_return = val;
00328         setcontext(&toplevel);
00329         return -1;
00330     }
00331     else
00332     {
00333         // back via longjmp, because someone called run() again.  Let's go
00334         // back to our running task...
00335         valgrind_fix(stacktop);
00336         return newval;
00337     }
00338 }
00339 
00340 
00341 void WvTaskMan::get_stack(WvTask &task, size_t size)
00342 {
00343     context_return = 0;
00344     assert(getcontext(&get_stack_return) == 0);
00345     if (context_return == 0)
00346     {
00347         assert(magic_number == -WVTASK_MAGIC);
00348         assert(task.magic_number == WVTASK_MAGIC);
00349 
00350         if (!use_shared_stack())
00351         {
00352 #if defined(__linux__) && (defined(__386__) || defined(__i386) || defined(__i386__))
00353             static char *next_stack_addr = (char *)0xB0000000;
00354             static const size_t stack_shift = 0x00100000;
00355 
00356             next_stack_addr -= stack_shift;
00357 #else
00358             static char *next_stack_addr = NULL;
00359 #endif
00360         
00361             task.stack = mmap(next_stack_addr, task.stacksize,
00362                 PROT_READ | PROT_WRITE,
00363                 MAP_PRIVATE | MAP_ANONYMOUS,
00364                 -1, 0);
00365         }
00366         
00367         // initial setup
00368         stack_target = &task;
00369         context_return = size/1024 + (size%1024 > 0);
00370         setcontext(&stackmaster_task);
00371     }
00372     else
00373     {
00374         if (current_task)
00375             valgrind_fix(stacktop);
00376         assert(magic_number == -WVTASK_MAGIC);
00377         assert(task.magic_number == WVTASK_MAGIC);
00378         
00379         // back from stackmaster - the task is now set up.
00380         return;
00381     }
00382 }
00383 
00384 
00385 void WvTaskMan::stackmaster()
00386 {
00387     // leave lots of room on the "main" stack before doing our magic
00388     alloca(1024*1024);
00389     
00390     _stackmaster();
00391 }
00392 
00393 
00394 void WvTaskMan::_stackmaster()
00395 {
00396     int val;
00397     size_t total;
00398     
00399     Dprintf("stackmaster 1\n");
00400     
00401     // the main loop runs once from the constructor, and then once more
00402     // after each stack allocation.
00403     for (;;)
00404     {
00405         assert(magic_number == -WVTASK_MAGIC);
00406         
00407         context_return = 0;
00408         assert(getcontext(&stackmaster_task) == 0);
00409         val = context_return;
00410         if (val == 0)
00411         {
00412             assert(magic_number == -WVTASK_MAGIC);
00413             
00414             // just did setjmp; save stackmaster's current state (with
00415             // all current stack allocations) and go back to get_stack
00416             // (or the constructor, if that's what called us)
00417             context_return = 1;
00418             setcontext(&get_stack_return);
00419         }
00420         else
00421         {
00422             valgrind_fix(stacktop);
00423             assert(magic_number == -WVTASK_MAGIC);
00424             
00425             total = (val+1) * (size_t)1024;
00426             
00427             if (!use_shared_stack())
00428                 total = 1024; // enough to save the do_task stack frame
00429 
00430             // set up a stack frame for the new task.  This runs once
00431             // per get_stack.
00432             //alloc_stack_and_switch(total);
00433             do_task();
00434             
00435             assert(magic_number == -WVTASK_MAGIC);
00436 
00437             // allocate the stack area so we never use it again
00438             alloca(total);
00439 
00440             // a little sentinel so we can detect stack overflows
00441             stack_target->stack_magic = (int *)alloca(sizeof(int));
00442             *stack_target->stack_magic = WVTASK_MAGIC;
00443             
00444             // clear the stack to 0x42 so we can count unused stack
00445             // space later.
00446 #if TASK_DEBUG
00447             memset(stack_target->stack_magic + 1, 0x42, total - 1024);
00448 #endif
00449         }
00450     }
00451 }
00452 
00453 
00454 void WvTaskMan::call_func(WvTask *task)
00455 {
00456     Dprintf("WvTaskMan: calling task #%d (%s)\n",
00457             task->tid, (const char *)task->name);
00458     task->func(task->userdata);
00459     Dprintf("WvTaskMan: returning from task #%d (%s)\n",
00460             task->tid, (const char *)task->name);
00461     context_return = 1;
00462 }
00463 
00464 
00465 void WvTaskMan::do_task()
00466 {
00467     assert(magic_number == -WVTASK_MAGIC);
00468     WvTask *task = stack_target;
00469     assert(task->magic_number == WVTASK_MAGIC);
00470         
00471     // back here from longjmp; someone wants stack space.    
00472     context_return = 0;
00473     assert(getcontext(&task->mystate) == 0);
00474     if (context_return == 0)
00475     {
00476         // done the setjmp; that means the target task now has
00477         // a working jmp_buf all set up.  Leave space on the stack
00478         // for his data, then repeat the loop in _stackmaster (so we can
00479         // return to get_stack(), and allocate more stack for someone later)
00480         // 
00481         // Note that nothing on the allocated stack needs to be valid; when
00482         // they longjmp to task->mystate, they'll have a new stack pointer
00483         // and they'll already know what to do (in the 'else' clause, below)
00484         Dprintf("stackmaster 5\n");
00485         return;
00486     }
00487     else
00488     {
00489         // someone did a run() on the task, which
00490         // means they're ready to make it go.  Do it.
00491         valgrind_fix(stacktop);
00492         for (;;)
00493         {
00494             assert(magic_number == -WVTASK_MAGIC);
00495             assert(task);
00496             assert(task->magic_number == WVTASK_MAGIC);
00497             
00498             if (task->func && task->running)
00499             {
00500                 if (use_shared_stack())
00501                 {
00502                     // this is the task's main function.  It can call yield()
00503                     // to give up its timeslice if it wants.  Either way, it
00504                     // only returns to *us* if the function actually finishes.
00505                     task->func(task->userdata);
00506                 }
00507                 else
00508                 {
00509                     assert(getcontext(&task->func_call) == 0);
00510                     task->func_call.uc_stack.ss_size = task->stacksize;
00511                     task->func_call.uc_stack.ss_sp = task->stack;
00512                     task->func_call.uc_stack.ss_flags = 0;
00513                     task->func_call.uc_link = &task->func_return;
00514                     Dprintf("WvTaskMan: makecontext #%d (%s)\n",
00515                             task->tid, (const char *)task->name);
00516                     makecontext(&task->func_call,
00517                             (void (*)(void))call_func, 1, task);
00518 
00519                     context_return = 0;
00520                     assert(getcontext(&task->func_return) == 0);
00521                     if (context_return == 0)
00522                         setcontext(&task->func_call);
00523                 }
00524                 
00525                 // the task's function terminated.
00526                 task->name = "DEAD";
00527                 task->running = false;
00528                 task->numrunning--;
00529             }
00530             yield();
00531         }
00532     }
00533 }
00534 
00535 
00536 const void *WvTaskMan::current_top_of_stack()
00537 {
00538 #ifdef HAVE_LIBC_STACK_END
00539     extern const void *__libc_stack_end;
00540     if (use_shared_stack() || current_task == NULL)
00541         return __libc_stack_end;
00542     else
00543         return (const char *)current_task->stack + current_task->stacksize;
00544 #else
00545     return 0;
00546 #endif
00547 }
00548 
00549 
00550 size_t WvTaskMan::current_stacksize_limit()
00551 {
00552     if (use_shared_stack() || current_task == NULL)
00553     {
00554         struct rlimit rl;
00555         if (getrlimit(RLIMIT_STACK, &rl) == 0)
00556             return size_t(rl.rlim_cur);
00557         else
00558             return 0;
00559     }
00560     else
00561         return size_t(current_task->stacksize);
00562 }
00563 
00564