WvStreams
|
00001 /* 00002 * Worldvisions Weaver Software: 00003 * Copyright (C) 1997-2002 Net Integration Technologies, Inc. 00004 * 00005 * WvCont provides "continuations", which are apparently also known as 00006 * semi-coroutines. See wvcont.h for more details. 00007 */ 00008 #include "wvcont.h" 00009 #include "wvtask.h" 00010 #include "wvlinklist.h" 00011 #include <assert.h> 00012 00013 // private data that doesn't need to be in the header 00014 struct WvCont::Data 00015 { 00016 int links; // the refcount of this Data object 00017 int mydepth; // this task's depth in the call stack 00018 bool finishing; // true if we're trying to terminate this task ASAP 00019 // (generally because WvCont is being destroyed) 00020 size_t stacksize; 00021 WvTaskMan *taskman; 00022 WvTask *task; 00023 00024 WvContCallback cb; // the callback we want to call inside our WvTask 00025 void *ret; 00026 void *p1; 00027 00028 Data(const WvContCallback &_cb, size_t _stacksize) : cb(_cb) 00029 { links = 1; finishing = false; stacksize = _stacksize; mydepth = 0; 00030 taskman = WvTaskMan::get(); 00031 task = NULL; report(); 00032 if (data_list == NULL) 00033 data_list = new DataList; 00034 data_list->append(this, false); 00035 } 00036 ~Data(); 00037 00038 void link() 00039 { links++; report(); } 00040 void unlink() 00041 { links--; report(); if (!links) delete this; } 00042 00043 void report() 00044 { /* printf("%p: links=%d\n", this, links); */ } 00045 }; 00046 00047 00048 WvCont::Data *WvCont::curdata = NULL; 00049 int WvCont::taskdepth = 0; 00050 00051 00052 WvCont::DataList *WvCont::data_list = NULL; 00053 00054 00055 WvCont::WvCont(const WvCont &cb) 00056 { 00057 static bool first = true; 00058 if (first) 00059 { 00060 first = false; 00061 WvStreamsDebugger::add_command("conts", 0, 00062 debugger_conts_run_cb, 0); 00063 } 00064 00065 data = cb.data; 00066 data->link(); 00067 } 00068 00069 00070 WvCont::WvCont(const WvContCallback &cb, unsigned long _stacksize) 00071 { 00072 data = new Data(cb, (size_t)_stacksize); 00073 } 00074 00075 00076 WvCont::WvCont(Data *data) 00077 { 00078 this->data = data; 00079 data->link(); 00080 } 00081 00082 00083 WvCont::~WvCont() 00084 { 00085 if (data->links == 1) // I'm the last link, and it's not currently running 00086 { 00087 data->finishing = true; 00088 data->p1 = NULL; // don't re-pass invalid data 00089 while (data->task && data->task->isrunning()) 00090 call(); 00091 } 00092 00093 data->unlink(); 00094 } 00095 00096 00097 WvCont::Data::~Data() 00098 { 00099 assert(!links); 00100 00101 if (task) 00102 task->recycle(); 00103 taskman->unlink(); 00104 //printf("%p: deleting\n", this); 00105 report(); 00106 00107 data_list->unlink(this); 00108 if (data_list->isempty()) 00109 { 00110 delete data_list; 00111 data_list = NULL; 00112 } 00113 } 00114 00115 00116 static inline const char *Yes_No(bool val) 00117 { 00118 return val? "Yes": "No"; 00119 } 00120 00121 00122 WvString WvCont::debugger_conts_run_cb(WvStringParm cmd, WvStringList &args, 00123 WvStreamsDebugger::ResultCallback result_cb, void *) 00124 { 00125 const char *format = "%5s%s%5s%s%9s%s%10s%s%7s%s%s"; 00126 WvStringList result; 00127 result.append(format, "Links", "-", "Depth", "-", "Finishing", "-", "Stack Size", 00128 "-", "Task ID", "-", "Task Name------"); 00129 result_cb(cmd, result); 00130 00131 if (!data_list) 00132 return WvString::null; 00133 00134 DataList::Iter i(*data_list); 00135 for (i.rewind(); i.next(); ) 00136 { 00137 result.zap(); 00138 result.append(format, 00139 i->links, " ", i->mydepth, " ", Yes_No(i->finishing), " ", 00140 i->stacksize, " ", 00141 i->task? WvString(i->task->get_tid()): WvString("n/a"), " ", 00142 i->task? i->task->get_name(): WvString("n/a")); 00143 result_cb(cmd, result); 00144 } 00145 00146 return WvString::null; 00147 } 00148 00149 00150 // note: assumes data->task is already running! 00151 void *WvCont::_call(Data *data) 00152 { 00153 Data *olddata = curdata; 00154 curdata = data; 00155 data->link(); // don't delete this context while it's running! 00156 00157 // enforce the call stack. If we didn't do this, a yield() five calls 00158 // deep would return to the very top, rather to the second-innermost 00159 // context. 00160 // 00161 // Note that this implementation has the interesting side-effect of 00162 // short-circuiting recursion (a calls b, b calls c, c calls a), since 00163 // calling 'a' if it's already running means the same as "yield all the 00164 // way back to a", and this loop enforces one-level-at-a-time yielding. 00165 // 00166 // Because that behaviour is probably undesirable, we make 'mydepth' into 00167 // a member variable instead of just putting it on the stack. This is 00168 // only needed so that we can have the assert(). 00169 assert(!data->mydepth); 00170 data->mydepth = ++taskdepth; 00171 do 00172 { 00173 assert(data->task); 00174 do 00175 { 00176 data->taskman->run(*data->task); 00177 if (data->links == 1) 00178 { 00179 data->finishing = true; // make WvCont::isok() false 00180 data->p1 = NULL; // don't re-pass invalid data 00181 } 00182 } while (data->finishing && data->task && data->task->isrunning()); 00183 assert(data->links); 00184 } while (taskdepth > data->mydepth); 00185 assert(taskdepth == data->mydepth); 00186 taskdepth--; 00187 data->mydepth = 0; 00188 00189 void *ret = data->ret; 00190 data->unlink(); 00191 curdata = olddata; 00192 return ret; 00193 } 00194 00195 00196 void *WvCont::operator() (void *p1) 00197 { 00198 data->ret = reinterpret_cast<void*>(-42); 00199 00200 if (!data->task) 00201 data->task = data->taskman->start("wvcont", bouncer, data, 00202 data->stacksize); 00203 else if (!data->task->isrunning()) 00204 data->task->start("wvcont+", bouncer, data); 00205 00206 assert(data->task); 00207 00208 data->p1 = p1; 00209 return call(); 00210 } 00211 00212 00213 WvCont WvCont::current() 00214 { 00215 assert(curdata); 00216 assert(curdata->task == curdata->taskman->whoami()); 00217 assert(isok()); // this assertion is a bit aggressive... 00218 return WvCont(curdata); 00219 } 00220 00221 00222 void *WvCont::yield(void *ret) 00223 { 00224 assert(curdata); 00225 assert(curdata->task == curdata->taskman->whoami()); 00226 00227 // this assertion is a bit aggressive, but on purpose; a callback that 00228 // does yield() instead of returning when its context should be dying 00229 // is pretty badly behaved. 00230 assert(isok()); 00231 00232 curdata->ret = ret; 00233 curdata->taskman->yield(); 00234 return curdata->p1; 00235 } 00236 00237 00238 bool WvCont::isok() 00239 { 00240 // if we're not using WvCont, it's not okay to yield 00241 if (!curdata) 00242 return false; 00243 00244 assert(curdata->task == curdata->taskman->whoami()); 00245 return !curdata->finishing; 00246 } 00247 00248 00249 void WvCont::bouncer(void *userdata) 00250 { 00251 Data *data = (Data *)userdata; 00252 00253 // DON'T BE FOOLED! 00254 // all yield() calls stay inside the inner function; our return value 00255 // is only for the final run after data->cb() returns. 00256 data->ret = data->cb(data->p1); 00257 }