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 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 96class Killed(Exception): pass 97class EarlyExit(Exception): pass 98 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): 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