Cothread Architecture Notes¶
Some notes on key architectural issues in the cothread
scheduler.
Coroutine Switching¶
There are precisely 7 calls to switch()
in the cothread
library,
and it’s crucial that they work together correctly. These calls are (in order
of appearance in the code).
- During scheduler creation (in the
create()
method of_Scheduler
) we create a separate scheduler task and switch to that. This should be called from the main parent task (ie, when it ends, the application terminates). - As soon as the scheduler has been initialised, control is returned to the
main task. This is in
__scheduler()
, which carries a top level loop for restarting the scheduler. - If the main scheduler loop
__schedule_loop()
raises an exception, control is switched to the main parent task with a special__WAKEUP_INTERRUPT
return code. The main task will at this point normally be waiting for a normal scheduler wake up. - The
__tick()
dispatcher switches to all ready tasks in turn passing a wakeup code, either__WAKEUP_NORMAL
or__WAKEUP_TIMEOUT
. - If
poll_scheduler()
has been called, and thus__poll_callback()
is set, the routine__poll_suspend()
will switch back to the suspendedpoll_scheduler()
call. In this case special polling arguments are passed (this should be a list of (descriptor, flags) pairs, together with a timeout), and a list of active descriptors should be returned. poll_scheduler()
defines the other side of this switch: a list of ready descriptors is passed in a switch to the scheduler task, and the polling arguments described above are returned from thepoll_scheduler()
call – unless a__WAKEUP_INTERRUPT
switch has occurred!- The normal place where control is switched to the scheduler is in the middle
of
wait_until()
. We have to pass an empty list (to be compatible with thepoll_scheduler()
switch), and a wakeup reason should be returned.
Callback¶
The new Callback()
mechanism raises some issues.
- It is possible for the callback queue to grow without limit, and this is invisible to the poor users. This can happen if too much time is spent in processing updates.
- The callback queue is processed without yielding. If the callback queue is very long this can cause unresponsive behaviour. Unfortunately inserting a yield on every callback is surprisingly costly.
- We lack any mechanism for observing what’s going on inside the library.
Some notes for emulating select on Windows¶
Want to look at libevent. I suspect this, being a network API, is largely socket based, but there may be clues. Source file win32select.c, function win32_dispatch(), looks vaguely interesting. Also poll.c.
Also should look at twistedmatrix.com, and there’s a further page of notes in poll_win32.py
Anyhow, building on Windows is a pain and I dislike the platform anyway...
Questions on Stack Overflow.¶
http://stackoverflow.com/questions/3021424/select-on-a-named-pipe
The answer here is use the named pipe APIs using the overlapped I/O model and WaitForMultipleObjects(), example
http://stackoverflow.com/questions/3911799/windows-poll-or-select-on-named-pipe
Answer is need to use overlapped I/O or I/O completion ports, references
Another interesting suggestion is to use socketpair(2) instead of pipe(2) to create the asynchronous event notifier in _Callback and ThreadedEventQueue. This still leaves stdin handling for the input hook, but at least would restore some level of function. The socketpair function is in the Python socket module.
Ho ho ho. Turns out that Windows doesn’t have socketpair anyway! There’s a reasonable looking example here:
http://code.activestate.com/recipes/525487-extending-socketsocketpair-to-work-on-windows/
which does this:
def SocketPair(family = AF_INET; type_ = SOCK_STREAM; proto = IPPROTO_IP):
listensock = socket(family, type_, proto)
listensock.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)
listensock.bind( ('localhost', 0) )
iface, ephport = listensock.getsockname()
listensock.listen(1)
sock1 = socket(family, type_, proto)
connthread = threading.Thread(target=pairConnect, args=[sock1, ephport])
connthread.setDaemon(1)
connthread.start()
sock2, sock2addr = listensock.accept()
listensock.close()
return (sock1, sock2)
def pairConnect(sock, port):
sock.connect( ('localhost', port) )