WvStreams
wvcont.cc
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 }