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 
00036 #ifdef HAVE_VALGRIND_MEMCHECK_H
00037 #include <valgrind/memcheck.h>
00038 #else
00039 #define VALGRIND_MAKE_READABLE(x, y)
00040 #endif
00041 
00042 #define TASK_DEBUG 0
00043 #if TASK_DEBUG
00044 # define Dprintf(fmt, args...) fprintf(stderr, fmt, ##args)
00045 #else
00046 # define Dprintf(fmt, args...)
00047 #endif
00048 
00049 int WvTask::taskcount, WvTask::numtasks, WvTask::numrunning;
00050 
00051 WvTaskMan *WvTaskMan::singleton;
00052 int WvTaskMan::links, WvTaskMan::magic_number;
00053 WvTaskList WvTaskMan::free_tasks;
00054 jmp_buf WvTaskMan::stackmaster_task, WvTaskMan::get_stack_return,
00055     WvTaskMan::toplevel;
00056 WvTask *WvTaskMan::current_task, *WvTaskMan::stack_target;
00057 char *WvTaskMan::stacktop;
00058 
00059 
00060 static void valgrind_fix(char *stacktop)
00061 {
00062 #ifdef HAVE_VALGRIND_MEMCHECK_H
00063     char val;
00064     //printf("valgrind fix: %p-%p\n", &val, stacktop);
00065     assert(stacktop > &val);
00066 #endif
00067     VALGRIND_MAKE_READABLE(&val, stacktop - &val);
00068 }
00069 
00070 
00071 WvTask::WvTask(WvTaskMan &_man, size_t _stacksize) : man(_man)
00072 {
00073     stacksize = _stacksize;
00074     running = recycled = false;
00075     func = NULL;
00076     userdata = NULL;
00077     
00078     tid = ++taskcount;
00079     numtasks++;
00080     magic_number = WVTASK_MAGIC;
00081     stack_magic = NULL;
00082     
00083     man.get_stack(*this, stacksize);
00084 }
00085 
00086 
00087 WvTask::~WvTask()
00088 {
00089     numtasks--;
00090     if (running)
00091         numrunning--;
00092     magic_number = 42;
00093 }
00094 
00095 
00096 void WvTask::start(WvStringParm _name, TaskFunc *_func, void *_userdata)
00097 {
00098     assert(!recycled);
00099     name = _name;
00100     func = _func;
00101     userdata = _userdata;
00102     running = true;
00103     numrunning++;
00104 }
00105 
00106 
00107 void WvTask::recycle()
00108 {
00109     assert(!running);
00110     
00111     if (!running && !recycled)
00112     {
00113         man.free_tasks.append(this, true);
00114         recycled = true;
00115     }
00116 }
00117 
00118 
00119 WvTaskMan *WvTaskMan::get()
00120 {
00121     if (!links)
00122         singleton = new WvTaskMan;
00123     links++;
00124     return singleton;
00125 }
00126 
00127 
00128 void WvTaskMan::unlink()
00129 {
00130     links--;
00131     if (!links)
00132     {
00133         delete singleton;
00134         singleton = NULL;
00135     }
00136 }
00137 
00138 
00139 WvTaskMan::WvTaskMan()
00140 {
00141     stack_target = NULL;
00142     current_task = NULL;
00143     magic_number = -WVTASK_MAGIC;
00144     
00145     stacktop = (char *)alloca(0);
00146     
00147     if (setjmp(get_stack_return) == 0)
00148     {
00149         // initial setup - start the stackmaster() task (never returns!)
00150         stackmaster();
00151     }
00152     // if we get here, stackmaster did a longjmp back to us.
00153 }
00154 
00155 
00156 WvTaskMan::~WvTaskMan()
00157 {    
00158     magic_number = -42;
00159     free_tasks.zap();
00160 }
00161 
00162 
00163 WvTask *WvTaskMan::start(WvStringParm name, 
00164                          WvTask::TaskFunc *func, void *userdata,
00165                          size_t stacksize)
00166 {
00167     WvTask *t;
00168     
00169     WvTaskList::Iter i(free_tasks);
00170     for (i.rewind(); i.next(); )
00171     {
00172         if (i().stacksize >= stacksize)
00173         {
00174             t = &i();
00175             i.set_autofree(false);
00176             i.unlink();
00177             t->recycled = false;
00178             t->start(name, func, userdata);
00179             return t;
00180         }
00181     }
00182     
00183     // if we get here, no matching task was found.
00184     t = new WvTask(*this, stacksize);
00185     t->start(name, func, userdata);
00186     return t;
00187 }
00188 
00189 
00190 int WvTaskMan::run(WvTask &task, int val)
00191 {
00192     assert(magic_number == -WVTASK_MAGIC);
00193     assert(task.magic_number == WVTASK_MAGIC);
00194     assert(!task.recycled);
00195     
00196     Dprintf("WvTaskMan: running task #%d with value %d (%s)\n",
00197             task.tid, val, (const char *)task.name);
00198     
00199     if (&task == current_task)
00200         return val; // that's easy!
00201         
00202     WvTask *old_task = current_task;
00203     current_task = &task;
00204     jmp_buf *state;
00205     
00206     if (!old_task)
00207         state = &toplevel; // top-level call (not in an actual task yet)
00208     else
00209         state = &old_task->mystate;
00210     
00211     int newval = setjmp(*state);
00212     if (newval == 0)
00213     {
00214         // saved the state, now run the task.
00215         longjmp(task.mystate, val);
00216     }
00217     else
00218     {
00219         // need to make state readable to see if we need to make more readable..
00220         VALGRIND_MAKE_READABLE(&state, sizeof(state));
00221         // someone did yield() (if toplevel) or run() on our old task; done.
00222         if (state != &toplevel)
00223             valgrind_fix(stacktop);
00224         current_task = old_task;
00225         return newval;
00226     }
00227 }
00228 
00229 
00230 int WvTaskMan::yield(int val)
00231 {
00232     if (!current_task)
00233         return 0; // weird...
00234     
00235     Dprintf("WvTaskMan: yielding from task #%d with value %d (%s)\n",
00236            current_task->tid, val, (const char *)current_task->name);
00237     
00238     assert(current_task->stack_magic);
00239     
00240     // if this fails, this task overflowed its stack.  Make it bigger!
00241     VALGRIND_MAKE_READABLE(current_task->stack_magic,
00242                            sizeof(current_task->stack_magic));
00243     assert(*current_task->stack_magic == WVTASK_MAGIC);
00244 
00245 #if TASK_DEBUG
00246     size_t stackleft;
00247     char *stackbottom = (char *)(current_task->stack_magic + 1);
00248     for (stackleft = 0; stackleft < current_task->stacksize; stackleft++)
00249     {
00250         if (stackbottom[stackleft] != 0x42)
00251             break;
00252     }
00253     Dprintf("WvTaskMan: remaining stack after #%d (%s): %ld/%ld\n",
00254             current_task->tid, current_task->name.cstr(), (long)stackleft,
00255             (long)current_task->stacksize);
00256 #endif
00257                 
00258     int newval = setjmp(current_task->mystate);
00259     if (newval == 0)
00260     {
00261         // saved the task state; now yield to the toplevel.
00262         longjmp(toplevel, val);
00263     }
00264     else
00265     {
00266         // back via longjmp, because someone called run() again.  Let's go
00267         // back to our running task...
00268         valgrind_fix(stacktop);
00269         return newval;
00270     }
00271 }
00272 
00273 
00274 void WvTaskMan::get_stack(WvTask &task, size_t size)
00275 {
00276     if (setjmp(get_stack_return) == 0)
00277     {
00278         assert(magic_number == -WVTASK_MAGIC);
00279         assert(task.magic_number == WVTASK_MAGIC);
00280         
00281         // initial setup
00282         stack_target = &task;
00283         longjmp(stackmaster_task, size/1024 + (size%1024 > 0));
00284     }
00285     else
00286     {
00287         if (current_task)
00288             valgrind_fix(stacktop);
00289         assert(magic_number == -WVTASK_MAGIC);
00290         assert(task.magic_number == WVTASK_MAGIC);
00291         
00292         // back from stackmaster - the task is now set up.
00293         return;
00294     }
00295 }
00296 
00297 
00298 void WvTaskMan::stackmaster()
00299 {
00300     // leave lots of room on the "main" stack before doing our magic
00301     alloca(1024*1024);
00302     
00303     _stackmaster();
00304 }
00305 
00306 
00307 void WvTaskMan::_stackmaster()
00308 {
00309     int val;
00310     size_t total;
00311     
00312     Dprintf("stackmaster 1\n");
00313     
00314     // the main loop runs once from the constructor, and then once more
00315     // after each stack allocation.
00316     for (;;)
00317     {
00318         assert(magic_number == -WVTASK_MAGIC);
00319         
00320         val = setjmp(stackmaster_task);
00321         if (val == 0)
00322         {
00323             assert(magic_number == -WVTASK_MAGIC);
00324             
00325             // just did setjmp; save stackmaster's current state (with
00326             // all current stack allocations) and go back to get_stack
00327             // (or the constructor, if that's what called us)
00328             longjmp(get_stack_return, 1);
00329         }
00330         else
00331         {
00332             valgrind_fix(stacktop);
00333             assert(magic_number == -WVTASK_MAGIC);
00334             
00335             // set up a stack frame for the new task.  This runs once
00336             // per get_stack.
00337             do_task();
00338             
00339             assert(magic_number == -WVTASK_MAGIC);
00340             
00341             // allocate the stack area so we never use it again
00342             total = (val+1) * (size_t)1024;
00343             alloca(total);
00344 
00345             // a little sentinel so we can detect stack overflows
00346             stack_target->stack_magic = (int *)alloca(sizeof(int));
00347             *stack_target->stack_magic = WVTASK_MAGIC;
00348             
00349             // clear the stack to 0x42 so we can count unused stack
00350             // space later.
00351 #if TASK_DEBUG
00352             memset(stack_target->stack_magic + 1, 0x42, total - 1024);
00353 #endif
00354         }
00355     }
00356 }
00357 
00358 
00359 void WvTaskMan::do_task()
00360 {
00361     assert(magic_number == -WVTASK_MAGIC);
00362     WvTask *task = stack_target;
00363     assert(task->magic_number == WVTASK_MAGIC);
00364         
00365     // back here from longjmp; someone wants stack space.    
00366     if (setjmp(task->mystate) == 0)
00367     {
00368         // done the setjmp; that means the target task now has
00369         // a working jmp_buf all set up.  Leave space on the stack
00370         // for his data, then repeat the loop in _stackmaster (so we can
00371         // return to get_stack(), and allocate more stack for someone later)
00372         // 
00373         // Note that nothing on the allocated stack needs to be valid; when
00374         // they longjmp to task->mystate, they'll have a new stack pointer
00375         // and they'll already know what to do (in the 'else' clause, below)
00376         Dprintf("stackmaster 5\n");
00377         return;
00378     }
00379     else
00380     {
00381         // someone did a run() on the task, which
00382         // means they're ready to make it go.  Do it.
00383         valgrind_fix(stacktop);
00384         for (;;)
00385         {
00386             assert(magic_number == -WVTASK_MAGIC);
00387             assert(task);
00388             assert(task->magic_number == WVTASK_MAGIC);
00389             
00390             if (task->func && task->running)
00391             {
00392                 // this is the task's main function.  It can call yield()
00393                 // to give up its timeslice if it wants.  Either way, it
00394                 // only returns to *us* if the function actually finishes.
00395                 task->func(task->userdata);
00396                 
00397                 // the task's function terminated.
00398                 task->name = "DEAD";
00399                 task->running = false;
00400                 task->numrunning--;
00401             }
00402             yield();
00403         }
00404     }
00405 }

Generated on Mon Feb 5 10:54:30 2007 for WvStreams by  doxygen 1.5.1