X-Git-Url: https://git.distorted.org.uk/~mdw/tripe/blobdiff_plain/2aff5dbb7d9458dc30136e264b921a0c8c520c41..2fa800105a5897cad85c51c48d1970b9061a33f5:/py/rmcr.py diff --git a/py/rmcr.py b/py/rmcr.py new file mode 100644 index 00000000..61521b4d --- /dev/null +++ b/py/rmcr.py @@ -0,0 +1,208 @@ +### -*-python-*- +### +### Rich man's coroutines +### +### (c) 2006 Straylight/Edgeware +### + +###----- Licensing notice --------------------------------------------------- +### +### This file is part of Trivial IP Encryption (TrIPE). +### +### TrIPE is free software; you can redistribute it and/or modify +### it under the terms of the GNU General Public License as published by +### the Free Software Foundation; either version 2 of the License, or +### (at your option) any later version. +### +### TrIPE is distributed in the hope that it will be useful, +### but WITHOUT ANY WARRANTY; without even the implied warranty of +### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +### GNU General Public License for more details. +### +### You should have received a copy of the GNU General Public License +### along with TrIPE; if not, write to the Free Software Foundation, +### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + +__pychecker__ = 'self=me' + +###-------------------------------------------------------------------------- +### External dependencies. + +import thread as T +from sys import exc_info + +###-------------------------------------------------------------------------- +### What's going on? +### +### A coroutine is just a thread. Each coroutine has a recursive lock +### associated with it. The lock is almost always held. In order to switch +### to a coroutine, one releases its lock and then (re)locks one's own. + +###-------------------------------------------------------------------------- +### Low-level machinery. + +__debug = False +def _debug(msg): + if __debug: + print '+++ %s: %s' % (T.get_ident(), msg) + +def _switchto(cr, arg = None, exc = None): + """Switch to coroutine CR.""" + global active + _debug('> _switchto(%s, %s, %s)' % (cr, arg, exc)) + if not cr.livep: + raise ValueError, 'coroutine is dead' + cr._arg = arg + cr._exc = exc + if cr is active: + _debug(' _switchto: self-switch to %s' % cr) + else: + _debug(' _switchto: switch from %s to %s' % (active, cr)) + active = cr + _debug(' _switchto: %s releasing' % cr) + assert cr._lk.locked() + cr._lk.release() + _debug('< _switchto') + +###-------------------------------------------------------------------------- +### Coroutine object. + +def findvictim(cr): + """Find an appropriate victim coroutine for something, starting from CR.""" + while not cr: + if cr is None: + return main + cr = cr.parent + return cr + +class Coroutine (object): + """Heard of lightweight threads? Well, this is a heavyweight coroutine.""" + + def __init__(me, func = None, name = None, parent = None, __tid = None): + """ + Create a new coroutine object. + + The new coroutine is immediately activated. FUNC is the function to run, + and defaults to the coroutine object's `run' method, so subclassing is a + reasonable thing to do; NAME is a friendly name for the coroutine, and + shows up in debug traces. The __TID argument is used internally for + special effects, and shouldn't be used by external callers. + """ + global active + _debug('> __init__(%s, func = %s, tid = %s)' % (name, func, __tid)) + me.name = name + me._lk = T.allocate_lock() + _debug(' __init__: %s locking' % me) + me._lk.acquire() + me.livep = True + me._exc = None + me._arg = None + me.parent = parent or active + me._onexit = [None, None] + if __tid is not None: + me._tid = __tid + _debug(' __init__: manufacture cr %s with existing thread %d' % + (me, __tid)) + me._startp = False + else: + me._func = func or me.run + me._tid = T.start_new_thread(me._start, ()) + me._startp = True + assert me._lk.locked() + _debug(' __init__: create %s with new thread %d' % (me, me._tid)) + _debug('< __init__(%s)' % me) + + def __str__(me): + """Stringify a coroutine using its name if possible.""" + if me.name is not None: + return '' % me.name + else: + return repr(me) + + def _start(me): + """ + Start up the coroutine. + + Wait for this coroutine to become active, and then run the user's + function. When (if) that returns, mark the coroutine as dead. + """ + _debug('> _start(%s)' % (me)) + args, kwargs = me._wait() + _debug(' start(%s): args = %s, kwargs = %s' % (me, args, kwargs)) + me._startp = False + try: + try: + _debug(' _start(%s): call user (args = %s, kwargs = %s)' % + (me, args, kwargs)) + me._func(*args, **kwargs) + except: + _switchto(findvictim(me.parent), None, exc_info()) + finally: + _debug(' _start(%s): finally' % me) + _debug(' _start(%s): _onexit = %s' % (me, me._onexit)) + me.livep = False + _switchto(findvictim(me.parent), *me._onexit) + _debug('< _start(%s)' % me) + + def _wait(me): + """Wait for this coroutine to become active.""" + global active + _debug('> _wait(%s)' % me) + me._lk.acquire() + while me is not active: + _debug(' _wait(%s): locking' % me) + me._lk.acquire() + _debug(' _wait(%s): active, arg = %s, exc = %s' % + (me, me._arg, me._exc)) + if me._exc: + raise me._exc[0], me._exc[1], me._exc[2] + _debug('< _wait(%s): %s' % (me, me._arg)) + return me._arg + + def switch(me, *args, **kwargs): + """Switch to this coroutine, passing it the object OBJ.""" + global active + _debug('> switch(%s, args = %s, kwargs = %s)' % (me, args, kwargs)) + if me._startp: + obj = args, kwargs + else: + obj, = args or (None,) + assert not kwargs + old = active + _switchto(me, obj) + _debug('< switch') + return old._wait() + + def getcurrent(): + return active + getcurrent = staticmethod(getcurrent) + + def __nonzero__(me): + return me.livep + + def throw(me, exc, arg = None, tb = None): + """ + Switch to this coroutine, throwing it an exception. + + The exception is given by EXC, ARG and TB, which form the exception, + argument, traceback triple. + """ + global active + _debug('> throw(%s, %s, args = %s)' % (me, exc, arg)) + old = active + me._exc = [exc, arg, tb] + _switchto(me, None) + _debug('< throw') + return old._wait() + + def run(me, *args, **kw): + raise Exception('Coroutine has no body to run') + +###-------------------------------------------------------------------------- +### Setup stuff. + +active = None +main = Coroutine(_Coroutine__tid = T.get_ident(), name = '_main') +active = main + +###----- That's all, folks --------------------------------------------------