chiark / gitweb /
py: New Python module for writing services and suchlike
[tripe] / py / rmcr.py
diff --git a/py/rmcr.py b/py/rmcr.py
new file mode 100644 (file)
index 0000000..61521b4
--- /dev/null
@@ -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 '<Coroutine %s>' % 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 --------------------------------------------------