WvStreams
wvcont.cc
1 /*
2  * Worldvisions Weaver Software:
3  * Copyright (C) 1997-2002 Net Integration Technologies, Inc.
4  *
5  * WvCont provides "continuations", which are apparently also known as
6  * semi-coroutines. See wvcont.h for more details.
7  */
8 #include "wvcont.h"
9 #include "wvtask.h"
10 #include "wvlinklist.h"
11 #include <assert.h>
12 
13 // private data that doesn't need to be in the header
15 {
16  int links; // the refcount of this Data object
17  int mydepth; // this task's depth in the call stack
18  bool finishing; // true if we're trying to terminate this task ASAP
19  // (generally because WvCont is being destroyed)
20  size_t stacksize;
21  WvTaskMan *taskman;
22  WvTask *task;
23 
24  WvContCallback cb; // the callback we want to call inside our WvTask
25  void *ret;
26  void *p1;
27 
28  Data(const WvContCallback &_cb, size_t _stacksize) : cb(_cb)
29  { links = 1; finishing = false; stacksize = _stacksize; mydepth = 0;
30  taskman = WvTaskMan::get();
31  task = NULL; report();
32  if (data_list == NULL)
33  data_list = new DataList;
34  data_list->append(this, false);
35  }
36  ~Data();
37 
38  void link()
39  { links++; report(); }
40  void unlink()
41  { links--; report(); if (!links) delete this; }
42 
43  void report()
44  { /* printf("%p: links=%d\n", this, links); */ }
45 };
46 
47 
48 WvCont::Data *WvCont::curdata = NULL;
49 int WvCont::taskdepth = 0;
50 
51 
52 WvCont::DataList *WvCont::data_list = NULL;
53 
54 
55 WvCont::WvCont(const WvCont &cb)
56 {
57  static bool first = true;
58  if (first)
59  {
60  first = false;
61  WvStreamsDebugger::add_command("conts", 0,
62  debugger_conts_run_cb, 0);
63  }
64 
65  data = cb.data;
66  data->link();
67 }
68 
69 
70 WvCont::WvCont(const WvContCallback &cb, unsigned long _stacksize)
71 {
72  data = new Data(cb, (size_t)_stacksize);
73 }
74 
75 
76 WvCont::WvCont(Data *data)
77 {
78  this->data = data;
79  data->link();
80 }
81 
82 
84 {
85  if (data->links == 1) // I'm the last link, and it's not currently running
86  {
87  data->finishing = true;
88  data->p1 = NULL; // don't re-pass invalid data
89  while (data->task && data->task->isrunning())
90  call();
91  }
92 
93  data->unlink();
94 }
95 
96 
97 WvCont::Data::~Data()
98 {
99  assert(!links);
100 
101  if (task)
102  task->recycle();
103  taskman->unlink();
104  //printf("%p: deleting\n", this);
105  report();
106 
107  data_list->unlink(this);
108  if (data_list->isempty())
109  {
110  delete data_list;
111  data_list = NULL;
112  }
113 }
114 
115 
116 static inline const char *Yes_No(bool val)
117 {
118  return val? "Yes": "No";
119 }
120 
121 
122 WvString WvCont::debugger_conts_run_cb(WvStringParm cmd, WvStringList &args,
123  WvStreamsDebugger::ResultCallback result_cb, void *)
124 {
125  const char *format = "%5s%s%5s%s%9s%s%10s%s%7s%s%s";
126  WvStringList result;
127  result.append(format, "Links", "-", "Depth", "-", "Finishing", "-", "Stack Size",
128  "-", "Task ID", "-", "Task Name------");
129  result_cb(cmd, result);
130 
131  if (!data_list)
132  return WvString::null;
133 
134  DataList::Iter i(*data_list);
135  for (i.rewind(); i.next(); )
136  {
137  result.zap();
138  result.append(format,
139  i->links, " ", i->mydepth, " ", Yes_No(i->finishing), " ",
140  i->stacksize, " ",
141  i->task? WvString(i->task->get_tid()): WvString("n/a"), " ",
142  i->task? i->task->get_name(): WvString("n/a"));
143  result_cb(cmd, result);
144  }
145 
146  return WvString::null;
147 }
148 
149 
150 // note: assumes data->task is already running!
151 void *WvCont::_call(Data *data)
152 {
153  Data *olddata = curdata;
154  curdata = data;
155  data->link(); // don't delete this context while it's running!
156 
157  // enforce the call stack. If we didn't do this, a yield() five calls
158  // deep would return to the very top, rather to the second-innermost
159  // context.
160  //
161  // Note that this implementation has the interesting side-effect of
162  // short-circuiting recursion (a calls b, b calls c, c calls a), since
163  // calling 'a' if it's already running means the same as "yield all the
164  // way back to a", and this loop enforces one-level-at-a-time yielding.
165  //
166  // Because that behaviour is probably undesirable, we make 'mydepth' into
167  // a member variable instead of just putting it on the stack. This is
168  // only needed so that we can have the assert().
169  assert(!data->mydepth);
170  data->mydepth = ++taskdepth;
171  do
172  {
173  assert(data->task);
174  do
175  {
176  data->taskman->run(*data->task);
177  if (data->links == 1)
178  {
179  data->finishing = true; // make WvCont::isok() false
180  data->p1 = NULL; // don't re-pass invalid data
181  }
182  } while (data->finishing && data->task && data->task->isrunning());
183  assert(data->links);
184  } while (taskdepth > data->mydepth);
185  assert(taskdepth == data->mydepth);
186  taskdepth--;
187  data->mydepth = 0;
188 
189  void *ret = data->ret;
190  data->unlink();
191  curdata = olddata;
192  return ret;
193 }
194 
195 
196 void *WvCont::operator() (void *p1)
197 {
198  data->ret = reinterpret_cast<void*>(-42);
199 
200  if (!data->task)
201  data->task = data->taskman->start("wvcont", bouncer, data,
202  data->stacksize);
203  else if (!data->task->isrunning())
204  data->task->start("wvcont+", bouncer, data);
205 
206  assert(data->task);
207 
208  data->p1 = p1;
209  return call();
210 }
211 
212 
214 {
215  assert(curdata);
216  assert(curdata->task == curdata->taskman->whoami());
217  assert(isok()); // this assertion is a bit aggressive...
218  return WvCont(curdata);
219 }
220 
221 
222 void *WvCont::yield(void *ret)
223 {
224  assert(curdata);
225  assert(curdata->task == curdata->taskman->whoami());
226 
227  // this assertion is a bit aggressive, but on purpose; a callback that
228  // does yield() instead of returning when its context should be dying
229  // is pretty badly behaved.
230  assert(isok());
231 
232  curdata->ret = ret;
233  curdata->taskman->yield();
234  return curdata->p1;
235 }
236 
237 
239 {
240  // if we're not using WvCont, it's not okay to yield
241  if (!curdata)
242  return false;
243 
244  assert(curdata->task == curdata->taskman->whoami());
245  return !curdata->finishing;
246 }
247 
248 
249 void WvCont::bouncer(void *userdata)
250 {
251  Data *data = (Data *)userdata;
252 
253  // DON'T BE FOOLED!
254  // all yield() calls stay inside the inner function; our return value
255  // is only for the final run after data->cb() returns.
256  data->ret = data->cb(data->p1);
257 }
WvList::append
void append(T *data, bool autofree, const char *id=NULL)
Appends the element to the end of the list.
Definition: wvlinklist.h:276
WvTaskMan
Provides co-operative multitasking support among WvTask instances.
Definition: wvtask.h:81
WvCont::~WvCont
~WvCont()
Destructor.
Definition: wvcont.cc:83
WvTask
Represents a single thread of control.
Definition: wvtask.h:34
WvString
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:329
WvTaskMan::get
static WvTaskMan * get()
get/dereference the singleton global WvTaskMan
Definition: wvtask.cc:139
WvCont
WvCont provides "continuations", which are apparently also known as semi-coroutines.
Definition: wvcont.h:29
WvCont::current
static WvCont current()
Get a copy of the current WvCont.
Definition: wvcont.cc:213
WvCont::isok
static bool isok()
Tell us if the current context is "okay", that is, not trying to die.
Definition: wvcont.cc:238
WvStringList
This is a WvList of WvStrings, and is a really handy way to parse strings.
Definition: wvstringlist.h:27
WvList
A linked list container class.
Definition: wvlinklist.h:197
WvCont::operator()
void * operator()(void *p1=0)
call the callback, making p1 the return value of yield() or the parameter to the function,...
Definition: wvcont.cc:196
WvCont::Data
Definition: wvcont.cc:14
WvCont::yield
static void * yield(void *ret=0)
"return" from the current callback, giving value 'ret' to the person who called us.
Definition: wvcont.cc:222