1 # Coroutine implementation using Python threads.
2 #
3 # Combines ideas from Guido's Generator module, and from the coroutine
4 # features of Icon and Simula 67.
5 #
6 # To run a collection of functions as coroutines, you need to create
7 # a Coroutine object to control them:
8 #    co = Coroutine()
9 # and then 'create' a subsidiary object for each function in the
10 # collection:
11 #    cof1 = co.create(f1 [, arg1, arg2, ...]) # [] means optional,
12 #    cof2 = co.create(f2 [, arg1, arg2, ...]) #... not list
13 #    cof3 = co.create(f3 [, arg1, arg2, ...])
14 # etc.  The functions need not be distinct; 'create'ing the same
15 # function multiple times gives you independent instances of the
16 # function.
17 #
18 # To start the coroutines running, use co.tran on one of the create'd
19 # functions; e.g., co.tran(cof2).  The routine that first executes
20 # co.tran is called the "main coroutine".  It's special in several
21 # respects:  it existed before you created the Coroutine object; if any of
22 # the create'd coroutines exits (does a return, or suffers an unhandled
23 # exception), EarlyExit error is raised in the main coroutine; and the
24 # co.detach() method transfers control directly to the main coroutine
25 # (you can't use co.tran() for this because the main coroutine doesn't
26 # have a name ...).
27 #
28 # Coroutine objects support these methods:
29 #
30 # handle = .create(func [, arg1, arg2, ...])
31 #    Creates a coroutine for an invocation of func(arg1, arg2, ...),
32 #    and returns a handle ("name") for the coroutine so created.  The
33 #    handle can be used as the target in a subsequent .tran().
34 #
35 # .tran(target, data=None)
36 #    Transfer control to the create'd coroutine "target", optionally
37 #    passing it an arbitrary piece of data. To the coroutine A that does
38 #    the .tran, .tran acts like an ordinary function call:  another
39 #    coroutine B can .tran back to it later, and if it does A's .tran
40 #    returns the 'data' argument passed to B's tran.  E.g.,
41 #
42 #    in coroutine coA   in coroutine coC    in coroutine coB
43 #      x = co.tran(coC)   co.tran(coB)        co.tran(coA,12)
44 #      print x # 12
45 #
46 #    The data-passing feature is taken from Icon, and greatly cuts
47 #    the need to use global variables for inter-coroutine communication.
48 #
49 # .back( data=None )
50 #    The same as .tran(invoker, data=None), where 'invoker' is the
51 #    coroutine that most recently .tran'ed control to the coroutine
52 #    doing the .back.  This is akin to Icon's "&source".
53 #
54 # .detach( data=None )
55 #    The same as .tran(main, data=None), where 'main' is the
56 #    (unnameable!) coroutine that started it all.  'main' has all the
57 #    rights of any other coroutine:  upon receiving control, it can
58 #    .tran to an arbitrary coroutine of its choosing, go .back to
59 #    the .detach'er, or .kill the whole thing.
60 #
61 # .kill()
62 #    Destroy all the coroutines, and return control to the main
63 #    coroutine.  None of the create'ed coroutines can be resumed after a
64 #    .kill().  An EarlyExit exception does a .kill() automatically.  It's
65 #    a good idea to .kill() coroutines you're done with, since the
66 #    current implementation consumes a thread for each coroutine that
67 #    may be resumed.
68 
69 import thread
70 import sync
71 
72 class _CoEvent:
73     def __init__(self, func):
74         self.f = func
75         self.e = sync.event()
76 
77     def __repr__(self):
78         if self.f is None:
79             return 'main coroutine'
80         else:
81             return 'coroutine for func ' + self.f.func_name
82 
83     def __hash__(self):
84         return id(self)
85 
86     def __cmp__(x,y):
87         return cmp(id(x), id(y))
88 
89     def resume(self):
90         self.e.post()
91 
92     def wait(self):
93         self.e.wait()
94         self.e.clear()
95 
96 class Killed(Exception): pass
97 class EarlyExit(Exception): pass
98 
99 class Coroutine:
100     def __init__(self):
101         self.active = self.main = _CoEvent(None)
102         self.invokedby = {self.main: None}
103         self.killed = 0
104         self.value  = None
105         self.terminated_by = None
106 
107     def create(self, func, *args):
108         me = _CoEvent(func)
109         self.invokedby[me] = None
110         thread.start_new_thread(self._start, (me,) + args)
111         return me
112 
113     def _start(self, me, *args):
114         me.wait()
115         if not self.killed:
116             try:
117                 try:
118                     apply(me.f, args)
119                 except Killed:
120                     pass
121             finally:
122                 if not self.killed:
123                     self.terminated_by = me
124                     self.kill()
125 
126     def kill(self):
127         if self.killed:
128             raise TypeError, 'kill() called on dead coroutines'
129         self.killed = 1
130         for coroutine in self.invokedby.keys():
131             coroutine.resume()
132 
133     def back(self, data=None):
134         return self.tran( self.invokedby[self.active], data )
135 
136     def detach(self, data=None):
137         return self.tran( self.main, data )
138 
139     def tran(self, target, data=None):
140         if not self.invokedby.has_key(target):
141             raise TypeError, '.tran target %r is not an active coroutine' % (target,)
142         if self.killed:
143             raise TypeError, '.tran target %r is killed' % (target,)
144         self.value = data
145         me = self.active
146         self.invokedby[target] = me
147         self.active = target
148         target.resume()
149 
150         me.wait()
151         if self.killed:
152             if self.main is not me:
153                 raise Killed
154             if self.terminated_by is not None:
155                 raise EarlyExit, '%r terminated early' % (self.terminated_by,)
156 
157         return self.value
158 
159 # end of module
160