Debugging and Tracing — How Stackless differs¶
Debugging tools, like those used for tracing, are implemented throughcalls to thesys.settrace() function. Now, in standardPython®, whenthis has been called any code that runs within the operating system threadis covered by it. In Stackless however, this function only covers thecurrent tasklet. Stackless provides the tasklet attributestrace_function andprofile_function toget and set the trace/profile function of a particular tasklet.
The debugging related modules, whetherin the standard library or not, do not take this difference into account. They are notlikely to work, and if they do, are not likely to work in the way you expect.In an ideal world,Stackless-Python might include modified versions of thesemodules, and patches adding them would be most welcome.
If you want working debugging forStackless-Python, at this time your bestoption is to use theWingWare Python IDEor theEclipse IDE with the PyDev-Plugin.Both have gone out of their way to add and supportStackless-Python development.
Note
In the past, the possibility of ditching the per-tasklet behaviour forthe standard per-thread behaviour has been broached on the mailing list.Given the lack of movement on usability for this part of Stackless, it isnot unlikely that this suggested change will be revisited.
Tracing tasklets¶
In order to get debugging support working on a per-tasklet basis, you need toensure you enable tracing for all tasklets. This can be archived by theschedule callback. This callback sees every task switch. Here isa complete example:
from__future__importabsolute_import,print_functionimportsysimportstacklessimporttracebackclassNamedTasklet(stackless.tasklet):__slots__=("name",)def__init__(self,func,name=None):stackless.tasklet.__init__(self,func)ifnameisNone:name="at%08x"%(id(self))self.name=namedef__repr__(self):return"<tasklet%s>"%(self.name)classMutex(object):def__init__(self,capacity=1):self.queue=stackless.channel()self.capacity=capacitydefisLocked(self):'''return non-zero if locked'''returnself.capacity==0deflock(self):'''acquire the lock'''currentTasklet=stackless.getcurrent()atomic=currentTasklet.set_atomic(True)try:ifself.capacity:self.capacity-=1else:self.queue.receive()finally:currentTasklet.set_atomic(atomic)defunlock(self):'''release the lock'''currentTasklet=stackless.getcurrent()atomic=currentTasklet.set_atomic(True)try:ifself.queue.balance<0:self.queue.send(None)else:self.capacity+=1finally:currentTasklet.set_atomic(atomic)m=Mutex()deftask():name=stackless.getcurrent().nameprint(name,"acquiring")m.lock()print(name,"switching")stackless.schedule()print(name,"releasing")m.unlock()deftrace_function(frame,event,arg):ifframe.f_code.co_namein('schedule_cb','channel_cb'):returnNoneprint(" trace_function:%s%s in%s, line%s"%(stackless.current,event,frame.f_code.co_name,frame.f_lineno))ifeventin('call','line','exception'):returntrace_functionreturnNonedefchannel_cb(channel,tasklet,sending,willblock):tf=tasklet.trace_functiontry:tasklet.trace_function=Noneprint("Channel CB, tasklet%r,%s%s"%(tasklet,("recv","send")[sending],(""," will block")[willblock]))finally:tasklet.trace_function=tfdefschedule_cb(prev,next):# During a tasklet switch (during the execution of this function) the# the result of stackless.getcurrent() is implementation defined.# Therefore this function avoids any assumptions about the current tasklet.current_tf=sys.gettrace()try:sys.settrace(None)# don't trace this callbackcurrent_frame=sys._getframe()ifcurrent_tfisNone:# also look at the previous frame, in case this callback is exempt# from tracingf_back=current_frame.f_backiff_backisnotNone:current_tf=f_back.f_tracecurrent_info="Schedule CB "ifnotprev:print("%sstarting%r"%(current_info,next))elifnotnext:print("%sending%r"%(current_info,prev))else:print("%sjumping from%s to%s"%(current_info,prev,next))# Inform about the installed trace functionsprev_tf=current_tfifprev.frameiscurrent_frameelseprev.trace_functionnext_tf=current_tfifnext.frameiscurrent_frameelsenext.trace_functionprint(" Current trace functions: prev:%r, next:%r"%(prev_tf,next_tf))# Eventually set a trace functionifnextisnotNone:ifnotnext.is_main:tf=trace_functionelse:tf=Noneprint(" Setting trace function for next:%r"%(tf,))# Set the "global" trace function for the taskletnext.trace_function=tf# Set the "local" trace function for each frame# This is required, if the tasklet is already runningframe=next.frameifframeiscurrent_frame:frame=frame.f_backwhileframeisnotNone:frame.f_trace=tfframe=frame.f_backexcept:traceback.print_exc()finally:sys.settrace(current_tf)if__name__=="__main__":iflen(sys.argv)>1andsys.argv[1]=='hard':stackless.enable_softswitch(False)stackless.set_channel_callback(channel_cb)stackless.set_schedule_callback(schedule_cb)NamedTasklet(task,"tick")()NamedTasklet(task,"trick")()NamedTasklet(task,"track")()stackless.run()stackless.set_channel_callback(None)stackless.set_schedule_callback(None)
settrace and tasklets¶
Note
This section is out dated and only of historical interest. Since theimplementation oftrace_function andprofile_function a debugger can enable tracing or profilingwithin the schedule callback without monkey patching.
In order to get debugging support working on a per-tasklet basis, you need toensure you callsys.settrace() for all tasklets. Vilhelm Saevarssonhas an emailgiving code and a description of the steps required including potentiallyunforeseen circumstances, in the Stackless mailing list archives.
Vilhelm’s code:
importsysimportstacklessdefcontextDispatch(prev,next):ifnotprev:#Creating next# I never see this print outprint("Creating ",next)elifnotnext:#Destroying prev# I never see this print out eitherprint("Destroying ",prev)else:# Prev is being suspended# Next is resuming# When worker tasklets are resuming and have# not been set to trace, we make sure that# they are tracing before they run againifnotnext.frame.f_trace:# We might already be tracing so ...sys.call_tracing(next.settrace,(traceDispatch,))stackless.set_schedule_callback(contextDispatch)def__call__(self,*args,**kwargs):f=self.tempvaldefnew_f(old_f,args,kwargs):sys.settrace(traceDispatch)old_f(*args,**kwargs)sys.settrace(None)self.tempval=new_fstackless.tasklet.setup(self,f,args,kwargs)defsettrace(self,tb):self.frame.f_trace=tbsys.settrace(tb)stackless.tasklet.__call__=__call__stackless.tasklet.settrace=settrace
The key actions taken by this code:
- Wrap the creation of tasklets, so that the debugging hook is installedwhen the tasklet is first run.
- Intercept scheduling events, so that tasklets that were created beforedebugging was engaged, have the debugging hook installed before they arerun again.
