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: | |
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 -------------------------------------------------- |