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 }
A WvFastString acts exactly like a WvString, but can take (const char *) strings without needing to a...
Definition: wvstring.h:93
~WvCont()
Destructor.
Definition: wvcont.cc:83
Represents a single thread of control.
Definition: wvtask.h:34
WvCont provides "continuations", which are apparently also known as semi-coroutines.
Definition: wvcont.h:29
static WvTaskMan * get()
get/dereference the singleton global WvTaskMan
Definition: wvtask.cc:139
void append(T *data, bool autofree, const char *id=NULL)
Appends the element to the end of the list.
Definition: wvlinklist.h:276
This is a WvList of WvStrings, and is a really handy way to parse strings.
Definition: wvstringlist.h:27
static bool isok()
Tell us if the current context is "okay", that is, not trying to die.
Definition: wvcont.cc:238
A linked list container class.
Definition: wvlinklist.h:197
WvString is an implementation of a simple and efficient printable-string class.
Definition: wvstring.h:329
static void * yield(void *ret=0)
"return" from the current callback, giving value 'ret' to the person who called us.
Definition: wvcont.cc:222
Provides co-operative multitasking support among WvTask instances.
Definition: wvtask.h:81
static WvCont current()
Get a copy of the current WvCont.
Definition: wvcont.cc:213
void * operator()(void *p1=0)
call the callback, making p1 the return value of yield() or the parameter to the function, and returning Ret, the argument of yield() or the return value of the function.
Definition: wvcont.cc:196