blob: 5de2b62d1b49a9ba522dda7bffd4bb16652976f3 [file] [log] [blame]
Guido van Rossumf4e13a42000-11-08 15:17:49 +00001# 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
69import thread
70import sync
71
72class _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
Andrew M. Kuchlingd9a9c102008-09-13 01:57:25 +000096class Killed(Exception): pass
97class EarlyExit(Exception): pass
Guido van Rossumf4e13a42000-11-08 15:17:49 +000098
99class 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):
Walter Dörwald70a6b492004-02-12 17:35:32 +0000141 raise TypeError, '.tran target %r is not an active coroutine' % (target,)
Guido van Rossumf4e13a42000-11-08 15:17:49 +0000142 if self.killed:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000143 raise TypeError, '.tran target %r is killed' % (target,)
Guido van Rossumf4e13a42000-11-08 15:17:49 +0000144 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:
Walter Dörwald70a6b492004-02-12 17:35:32 +0000155 raise EarlyExit, '%r terminated early' % (self.terminated_by,)
Guido van Rossumf4e13a42000-11-08 15:17:49 +0000156
157 return self.value
158
159# end of module