chiark / gitweb /
Add new `knock' protocol.
[tripe] / py / rmcr.py
CommitLineData
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
30import thread as T
6a2ebadb 31from 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
44def _debug(msg):
45 if __debug:
46 print '+++ %s: %s' % (T.get_ident(), msg)
47
48def _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
69def 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
77class 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
205active = None
206main = Coroutine(_Coroutine__tid = T.get_ident(), name = '_main')
207active = main
208
209###----- That's all, folks --------------------------------------------------