"""The Stackless module allows you to do multitasking without using threads.The essential objects are tasklets and channels.Please refer to their documentation."""import_continuationclassTaskletExit(Exception):passCoroutineExit=TaskletExitdef_coroutine_getcurrent():"Returns the current coroutine (i.e. the one which called this function)."try:return_tls.current_coroutineexceptAttributeError:# first call in this thread: current == mainreturn_coroutine_getmain()def_coroutine_getmain():try:return_tls.main_coroutineexceptAttributeError:# create the main coroutine for this threadcontinulet=_continuation.continuletmain=coroutine()main._frame=continulet.__new__(continulet)main._is_started=-1_tls.current_coroutine=_tls.main_coroutine=mainreturn_tls.main_coroutineclasscoroutine(object):_is_started=0# 0=no, 1=yes, -1=maindef__init__(self):self._frame=Nonedefbind(self,func,*argl,**argd):"""coro.bind(f, *argl, **argd) -> None. binds function f to coro. f will be called with arguments *argl, **argd """ifself.is_alive:raiseValueError("cannot bind a bound coroutine")defrun(c):_tls.current_coroutine=selfself._is_started=1returnfunc(*argl,**argd)self._is_started=0self._frame=_continuation.continulet(run)defswitch(self):"""coro.switch() -> returnvalue switches to coroutine coro. If the bound function f finishes, the returnvalue is that of f, otherwise None is returned """current=_coroutine_getcurrent()try:current._frame.switch(to=self._frame)finally:_tls.current_coroutine=currentdefkill(self):"""coro.kill() : kill coroutine coro"""current=_coroutine_getcurrent()try:current._frame.throw(CoroutineExit,to=self._frame)finally:_tls.current_coroutine=current@propertydefis_alive(self):returnself._is_started<0or(self._frameisnotNoneandself._frame.is_pending())@propertydefis_zombie(self):returnself._is_started>0andnotself._frame.is_pending()getcurrent=staticmethod(_coroutine_getcurrent)def__reduce__(self):ifself._is_started<0:return_coroutine_getmain,()else:returntype(self),(),self.__dict__try:fromthreadimport_localexceptImportError:class_local(object):# assume no threadspass_tls=_local()# ____________________________________________________________fromcollectionsimportdequeimportoperator__all__='run getcurrent getmain schedule tasklet channel coroutine'.split()_global_task_id=0_squeue=None_main_tasklet=None_main_coroutine=None_last_task=None_channel_callback=None_schedule_callback=Nonedef_scheduler_remove(value):try:del_squeue[operator.indexOf(_squeue,value)]exceptValueError:passdef_scheduler_append(value,normal=True):ifnormal:_squeue.append(value)else:_squeue.rotate(-1)_squeue.appendleft(value)_squeue.rotate(1)def_scheduler_contains(value):try:operator.indexOf(_squeue,value)returnTrueexceptValueError:returnFalsedef_scheduler_switch(current,next):global_last_taskprev=_last_taskif(_schedule_callbackisnotNoneandprevisnotnext):_schedule_callback(prev,next)_last_task=nextassertnotnext.blockedifnextisnotcurrent:next.switch()returncurrentdefset_schedule_callback(callback):global_schedule_callback_schedule_callback=callbackdefset_channel_callback(callback):global_channel_callback_channel_callback=callbackdefgetruncount():returnlen(_squeue)classbomb(object):def__init__(self,exp_type=None,exp_value=None,exp_traceback=None):self.type=exp_typeself.value=exp_valueself.traceback=exp_tracebackdefraise_(self):raiseself.type,self.value,self.traceback##classchannel(object):""" A channel object is used for communication between tasklets. By sending on a channel, a tasklet that is waiting to receive is resumed. If there is no waiting receiver, the sender is suspended. By receiving from a channel, a tasklet that is waiting to send is resumed. If there is no waiting sender, the receiver is suspended. Attributes: preference ---------- -1: prefer receiver 0: don't prefer anything 1: prefer sender Pseudocode that shows in what situation a schedule happens: def send(arg): if !receiver: schedule() elif schedule_all: schedule() else: if (prefer receiver): schedule() else (don't prefer anything, prefer sender): pass NOW THE INTERESTING STUFF HAPPENS def receive(): if !sender: schedule() elif schedule_all: schedule() else: if (prefer sender): schedule() else (don't prefer anything, prefer receiver): pass NOW THE INTERESTING STUFF HAPPENS schedule_all ------------ True: overwrite preference. This means that the current tasklet always schedules before returning from send/receive (it always blocks). (see Stackless/module/channelobject.c) """def__init__(self,label=''):self.balance=0self.closing=Falseself.queue=deque()self.label=labelself.preference=-1self.schedule_all=Falsedef__str__(self):return'channel[%s](%s,%s)'%(self.label,self.balance,self.queue)defclose(self):""" channel.close() -- stops the channel from enlarging its queue. If the channel is not empty, the flag 'closing' becomes true. If the channel is empty, the flag 'closed' becomes true. """self.closing=True@propertydefclosed(self):returnself.closingandnotself.queuedefopen(self):""" channel.open() -- reopen a channel. See channel.close. """self.closing=Falsedef_channel_action(self,arg,d):""" d == -1 : receive d == 1 : send the original CStackless has an argument 'stackl' which is not used here. 'target' is the peer tasklet to the current one """do_schedule=Falseassertabs(d)==1source=getcurrent()source.tempval=argifd>0:cando=self.balance<0dir=delse:cando=self.balance>0dir=0if_channel_callbackisnotNone:_channel_callback(self,source,dir,notcando)self.balance+=difcando:# communication 1): there is somebody waitingtarget=self.queue.popleft()source.tempval,target.tempval=target.tempval,source.tempvaltarget.blocked=0ifself.schedule_all:# always schedule _scheduler_append(target)do_schedule=Trueelifself.preference==-d:_scheduler_append(target,False)do_schedule=Trueelse:_scheduler_append(target)else:# communication 2): there is nobody waiting# if source.block_trap:# raise RuntimeError("this tasklet does not like to be blocked")# if self.closing:# raise StopIteration()source.blocked=dself.queue.append(source)_scheduler_remove(getcurrent())do_schedule=Trueifdo_schedule:schedule()retval=source.tempvalifisinstance(retval,bomb):retval.raise_()returnretvaldefreceive(self):""" channel.receive() -- receive a value over the channel. If no other tasklet is already sending on the channel, the receiver will be blocked. Otherwise, the receiver will continue immediately, and the sender is put at the end of the runnables list. The above policy can be changed by setting channel flags. """returnself._channel_action(None,-1)defsend_exception(self,exp_type,msg):self.send(bomb(exp_type,exp_type(msg)))defsend_sequence(self,iterable):foriteminiterable:self.send(item)defsend(self,msg):""" channel.send(value) -- send a value over the channel. If no other tasklet is already receiving on the channel, the sender will be blocked. Otherwise, the receiver will be activated immediately, and the sender is put at the end of the runnables list. """returnself._channel_action(msg,1)classtasklet(coroutine):""" A tasklet object represents a tiny task in a Python thread. At program start, there is always one running main tasklet. New tasklets can be created with methods from the stackless module. """tempval=Nonedef__new__(cls,func=None,label=''):res=coroutine.__new__(cls)res.label=labelres._task_id=Nonereturnresdef__init__(self,func=None,label=''):coroutine.__init__(self)self._init(func,label)def_init(self,func=None,label=''):global_global_task_idself.func=funcself.alive=Falseself.blocked=Falseself._task_id=_global_task_idself.label=label_global_task_id+=1def__str__(self):return'<tasklet[%s, %s]>'%(self.label,self._task_id)__repr__=__str__def__call__(self,*argl,**argd):returnself.setup(*argl,**argd)defbind(self,func):""" Binding a tasklet to a callable object. The callable is usually passed in to the constructor. In some cases, it makes sense to be able to re-bind a tasklet, after it has been run, in order to keep its identity. Note that a tasklet can only be bound when it doesn't have a frame. """ifnotcallable(func):raiseTypeError('tasklet function must be a callable')self.func=funcdefkill(self):""" tasklet.kill -- raise a TaskletExit exception for the tasklet. Note that this is a regular exception that can be caught. The tasklet is immediately activated. If the exception passes the toplevel frame of the tasklet, the tasklet will silently die. """ifnotself.is_zombie:# Killing the tasklet by throwing TaskletExit exception.coroutine.kill(self)_scheduler_remove(self)self.alive=Falsedefsetup(self,*argl,**argd):""" supply the parameters for the callable """ifself.funcisNone:raiseTypeError('cframe function must be callable')func=self.funcdef_func():try:try:coroutine.switch(back)func(*argl,**argd)exceptTaskletExit:passfinally:_scheduler_remove(self)self.alive=Falseself.func=Nonecoroutine.bind(self,_func)back=_coroutine_getcurrent()coroutine.switch(self)self.alive=True_scheduler_append(self)returnselfdefrun(self):self.insert()_scheduler_switch(getcurrent(),self)definsert(self):ifself.blocked:raiseRuntimeError,"You cannot run a blocked tasklet"ifnotself.alive:raiseRuntimeError,"You cannot run an unbound(dead) tasklet"_scheduler_append(self)defremove(self):ifself.blocked:raiseRuntimeError,"You cannot remove a blocked tasklet."ifselfisgetcurrent():raiseRuntimeError,"The current tasklet cannot be removed."# not sure if I will revive this " Use t=tasklet().capture()"_scheduler_remove(self)defgetmain():""" getmain() -- return the main tasklet. """return_main_taskletdefgetcurrent():""" getcurrent() -- return the currently executing tasklet. """curr=coroutine.getcurrent()ifcurris_main_coroutine:return_main_taskletelse:returncurr_run_calls=[]defrun():""" run_watchdog(timeout) -- run tasklets until they are all done, or timeout instructions have passed. Tasklets must provide cooperative schedule() calls. If the timeout is met, the function returns. The calling tasklet is put aside while the tasklets are running. It is inserted back after the function stops, right before the tasklet that caused a timeout, if any. If an exception occours, it will be passed to the main tasklet. Please note that the 'timeout' feature is not yet implemented """curr=getcurrent()_run_calls.append(curr)_scheduler_remove(curr)try:schedule()assertnot_squeuefinally:_scheduler_append(curr)defschedule_remove(retval=None):""" schedule(retval=stackless.current) -- switch to the next runnable tasklet. The return value for this call is retval, with the current tasklet as default. schedule_remove(retval=stackless.current) -- ditto, and remove self. """_scheduler_remove(getcurrent())r=schedule(retval)returnrdefschedule(retval=None):""" schedule(retval=stackless.current) -- switch to the next runnable tasklet. The return value for this call is retval, with the current tasklet as default. schedule_remove(retval=stackless.current) -- ditto, and remove self. """mtask=getmain()curr=getcurrent()ifretvalisNone:retval=currwhileTrue:if_squeue:if_squeue[0]iscurr:# If the current is at the head, skip it._squeue.rotate(-1)task=_squeue[0]#_squeue.rotate(-1)elif_run_calls:task=_run_calls.pop()else:raiseRuntimeError('No runnable tasklets left.')_scheduler_switch(curr,task)ifcurris_last_task:# We are in the tasklet we want to resume at this point.returnretvaldef_init():global_main_taskletglobal_global_task_idglobal_squeueglobal_last_task_global_task_id=0_main_tasklet=coroutine.getcurrent()_main_tasklet.__class__=tasklet# XXX HAAAAAAAAAAAAAAAAAAAAACK_last_task=_main_tasklettasklet._init.im_func(_main_tasklet,label='main')_squeue=deque()_scheduler_append(_main_tasklet)_init()