Main Page | Namespace List | Class Hierarchy | Alphabetical List | Class List | 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 Tue Oct 5 01:09:21 2004 for WvStreams by doxygen 1.3.7