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