chiark / gitweb /
svc/connect.in: Only check the configuration database once a minute.
[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, excepthook
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._onexit = [me._func(*args, **kwargs), None]
138       except:
139         exc = exc_info()
140         _debug('  _start(%s): caught exception (%s)' % (me, exc))
141         me._onexit = [None, exc]
142     finally:
143       _debug('  _start(%s): finally' % me)
144       _debug('  _start(%s): _onexit = %s' % (me, me._onexit))
145       me.livep = False
146       _switchto(findvictim(me.parent), *me._onexit)
147     _debug('< _start(%s)' % me)
148
149   def _wait(me):
150     """Wait for this coroutine to become active."""
151     global active
152     _debug('> _wait(%s)' % me)
153     me._lk.acquire()
154     while me is not active:
155       _debug('  _wait(%s): locking' % me)
156       me._lk.acquire()
157     _debug('  _wait(%s): active, arg = %s, exc = %s' %
158            (me, me._arg, me._exc))
159     if me._exc:
160       raise me._exc[0], me._exc[1], me._exc[2]
161     _debug('< _wait(%s): %s' % (me, me._arg))
162     return me._arg
163
164   def switch(me, *args, **kwargs):
165     """Switch to this coroutine, passing it the object OBJ."""
166     global active
167     _debug('> switch(%s, args = %s, kwargs = %s)' % (me, args, kwargs))
168     if me._startp:
169       obj = args, kwargs
170     else:
171       obj, = args or (None,)
172       assert not kwargs
173     old = active
174     _switchto(me, obj)
175     _debug('< switch')
176     return old._wait()
177
178   def getcurrent():
179     return active
180   getcurrent = staticmethod(getcurrent)
181
182   def __nonzero__(me):
183     return me.livep
184
185   def throw(me, exc, arg = None, tb = None):
186     """
187     Switch to this coroutine, throwing it an exception.
188
189     The exception is given by EXC, ARG and TB, which form the exception,
190     argument, traceback triple.
191     """
192     global active
193     _debug('> throw(%s, %s, args = %s)' % (me, exc, arg))
194     old = active
195     me._exc = [exc, arg, tb]
196     _switchto(me, None)
197     _debug('< throw')
198     return old._wait()
199
200   def run(me, *args, **kw):
201     raise Exception('Coroutine has no body to run')
202
203 ###--------------------------------------------------------------------------
204 ### Setup stuff.
205
206 active = None
207 main = Coroutine(_Coroutine__tid = T.get_ident(), name = '_main')
208 active = main
209
210 ###----- That's all, folks --------------------------------------------------