Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Namespace Members | Class Members | File Members | Related Pages

wvtask.cc

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

Generated on Wed Dec 15 15:08:11 2004 for WvStreams by  doxygen 1.3.9.1