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 | ### | |
11ad66c2 MW |
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. | |
2fa80010 | 16 | ### |
11ad66c2 MW |
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. | |
2fa80010 MW |
21 | ### |
22 | ### You should have received a copy of the GNU General Public License | |
11ad66c2 | 23 | ### along with TrIPE. If not, see <https://www.gnu.org/licenses/>. |
2fa80010 MW |
24 | |
25 | __pychecker__ = 'self=me' | |
26 | ||
27 | ###-------------------------------------------------------------------------- | |
28 | ### External dependencies. | |
29 | ||
30 | import thread as T | |
6a2ebadb | 31 | from sys import exc_info, excepthook |
2fa80010 MW |
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: | |
171206b5 | 53 | raise ValueError('coroutine is dead') |
2fa80010 MW |
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)) | |
6a2ebadb | 136 | me._onexit = [me._func(*args, **kwargs), None] |
2fa80010 | 137 | except: |
e41b17c8 MW |
138 | exc = exc_info() |
139 | _debug(' _start(%s): caught exception (%s)' % (me, exc)) | |
6a2ebadb | 140 | me._onexit = [None, exc] |
2fa80010 MW |
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 -------------------------------------------------- |