chiark / gitweb /
Make `tripe' be the default key type.
[tripe] / py / rmcr.py
1 ### -*-python-*-
2 ###
3 ### Rich man's coroutines
4 ###
5 ### (c) 2006 Straylight/Edgeware
6 ###
7
8 ###----- Licensing notice ---------------------------------------------------
9 ###
10 ### This file is part of Trivial IP Encryption (TrIPE).
11 ###
12 ### TrIPE is free software; you can redistribute it and/or modify
13 ### it under the terms of the GNU General Public License as published by
14 ### the Free Software Foundation; either version 2 of the License, or
15 ### (at your option) any later version.
16 ###
17 ### TrIPE is distributed in the hope that it will be useful,
18 ### but WITHOUT ANY WARRANTY; without even the implied warranty of
19 ### MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
20 ### GNU General Public License for more details.
21 ###
22 ### You should have received a copy of the GNU General Public License
23 ### along with TrIPE; if not, write to the Free Software Foundation,
24 ### Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
25
26 __pychecker__ = 'self=me'
27
28 ###--------------------------------------------------------------------------
29 ### External dependencies.
30
31 import thread as T
32 from sys import exc_info
33
34 ###--------------------------------------------------------------------------
35 ### What's going on?
36 ###
37 ### A coroutine is just a thread.  Each coroutine has a recursive lock
38 ### associated with it.  The lock is almost always held.  In order to switch
39 ### to a coroutine, one releases its lock and then (re)locks one's own.
40
41 ###--------------------------------------------------------------------------
42 ### Low-level machinery.
43
44 __debug = False
45 def _debug(msg):
46   if __debug:
47     print '+++ %s: %s' % (T.get_ident(), msg)
48
49 def _switchto(cr, arg = None, exc = None):
50   """Switch to coroutine CR."""
51   global active
52   _debug('> _switchto(%s, %s, %s)' % (cr, arg, exc))
53   if not cr.livep:
54     raise ValueError, 'coroutine is dead'
55   cr._arg = arg
56   cr._exc = exc
57   if cr is active:
58     _debug('  _switchto: self-switch to %s' % cr)
59   else:
60     _debug('  _switchto: switch from %s to %s' % (active, cr))
61     active = cr
62     _debug('  _switchto: %s releasing' % cr)
63     assert cr._lk.locked()
64     cr._lk.release()
65   _debug('< _switchto')
66
67 ###--------------------------------------------------------------------------
68 ### Coroutine object.
69
70 def findvictim(cr):
71   """Find an appropriate victim coroutine for something, starting from CR."""
72   while not cr:
73     if cr is None:
74       return main
75     cr = cr.parent
76   return cr
77
78 class Coroutine (object):
79   """Heard of lightweight threads?  Well, this is a heavyweight coroutine."""
80
81   def __init__(me, func = None, name = None, parent = None, __tid = None):
82     """
83     Create a new coroutine object.
84
85     The new coroutine is immediately activated.  FUNC is the function to run,
86     and defaults to the coroutine object's `run' method, so subclassing is a
87     reasonable thing to do; NAME is a friendly name for the coroutine, and
88     shows up in debug traces.  The __TID argument is used internally for
89     special effects, and shouldn't be used by external callers.
90     """
91     global active
92     _debug('> __init__(%s, func = %s, tid = %s)' % (name, func, __tid))
93     me.name = name
94     me._lk = T.allocate_lock()
95     _debug('  __init__: %s locking' % me)
96     me._lk.acquire()
97     me.livep = True
98     me._exc = None
99     me._arg = None
100     me.parent = parent or active
101     me._onexit = [None, None]
102     if __tid is not None:
103       me._tid = __tid
104       _debug('  __init__: manufacture cr %s with existing thread %d' %
105              (me, __tid))
106       me._startp = False
107     else:
108       me._func = func or me.run
109       me._tid = T.start_new_thread(me._start, ())
110       me._startp = True
111       assert me._lk.locked()
112       _debug('  __init__: create %s with new thread %d' % (me, me._tid))
113     _debug('< __init__(%s)' % me)
114
115   def __str__(me):
116     """Stringify a coroutine using its name if possible."""
117     if me.name is not None:
118       return '<Coroutine %s>' % me.name
119     else:
120       return repr(me)
121
122   def _start(me):
123     """
124     Start up the coroutine.
125
126     Wait for this coroutine to become active, and then run the user's
127     function.  When (if) that returns, mark the coroutine as dead.
128     """
129     _debug('> _start(%s)' % (me))
130     args, kwargs = me._wait()
131     _debug('  start(%s): args = %s, kwargs = %s' % (me, args, kwargs))
132     me._startp = False
133     try:
134       try:
135         _debug('  _start(%s): call user (args = %s, kwargs = %s)' %
136                (me, args, kwargs))
137         me._func(*args, **kwargs)
138       except:
139         _switchto(findvictim(me.parent), None, exc_info())
140     finally:
141       _debug('  _start(%s): finally' % me)
142       _debug('  _start(%s): _onexit = %s' % (me, me._onexit))
143       me.livep = False
144       _switchto(findvictim(me.parent), *me._onexit)
145     _debug('< _start(%s)' % me)
146
147   def _wait(me):
148     """Wait for this coroutine to become active."""
149     global active
150     _debug('> _wait(%s)' % me)
151     me._lk.acquire()
152     while me is not active:
153       _debug('  _wait(%s): locking' % me)
154       me._lk.acquire()
155     _debug('  _wait(%s): active, arg = %s, exc = %s' %
156            (me, me._arg, me._exc))
157     if me._exc:
158       raise me._exc[0], me._exc[1], me._exc[2]
159     _debug('< _wait(%s): %s' % (me, me._arg))
160     return me._arg
161
162   def switch(me, *args, **kwargs):
163     """Switch to this coroutine, passing it the object OBJ."""
164     global active
165     _debug('> switch(%s, args = %s, kwargs = %s)' % (me, args, kwargs))
166     if me._startp:
167       obj = args, kwargs
168     else:
169       obj, = args or (None,)
170       assert not kwargs
171     old = active
172     _switchto(me, obj)
173     _debug('< switch')
174     return old._wait()
175
176   def getcurrent():
177     return active
178   getcurrent = staticmethod(getcurrent)
179
180   def __nonzero__(me):
181     return me.livep
182
183   def throw(me, exc, arg = None, tb = None):
184     """
185     Switch to this coroutine, throwing it an exception.
186
187     The exception is given by EXC, ARG and TB, which form the exception,
188     argument, traceback triple.
189     """
190     global active
191     _debug('> throw(%s, %s, args = %s)' % (me, exc, arg))
192     old = active
193     me._exc = [exc, arg, tb]
194     _switchto(me, None)
195     _debug('< throw')
196     return old._wait()
197
198   def run(me, *args, **kw):
199     raise Exception('Coroutine has no body to run')
200
201 ###--------------------------------------------------------------------------
202 ### Setup stuff.
203
204 active = None
205 main = Coroutine(_Coroutine__tid = T.get_ident(), name = '_main')
206 active = main
207
208 ###----- That's all, folks --------------------------------------------------