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