Commit | Line | Data |
---|---|---|
2fa80010 MW |
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: | |
e41b17c8 MW |
139 | exc = exc_info() |
140 | _debug(' _start(%s): caught exception (%s)' % (me, exc)) | |
141 | _switchto(findvictim(me.parent), None, exc) | |
2fa80010 MW |
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 -------------------------------------------------- |