blob: c2fd508e05d49c96b7ab82db2966a0ba46e2df27 [file] [log] [blame]
from __future__ import nested_scopes
from pydevd_constants import * # @UnusedWildImport
import stackless # @UnresolvedImport
from pydevd_tracing import SetTrace
from pydevd_custom_frames import updateCustomFrame, removeCustomFrame, addCustomFrame
from pydevd_comm import GetGlobalDebugger
import weakref
from pydevd_file_utils import GetFilenameAndBase
from pydevd import DONT_TRACE
from pydevd_constants import DictItems
# Used so that we don't loose the id (because we'll remove when it's not alive and would generate a new id for the
# same tasklet).
class TaskletToLastId:
'''
So, why not a WeakKeyDictionary?
The problem is that removals from the WeakKeyDictionary will create a new tasklet (as it adds a callback to
remove the key when it's garbage-collected), so, we can get into a recursion.
'''
def __init__(self):
self.tasklet_ref_to_last_id = {}
self._i = 0
def get(self, tasklet):
return self.tasklet_ref_to_last_id.get(weakref.ref(tasklet))
def __setitem__(self, tasklet, last_id):
self.tasklet_ref_to_last_id[weakref.ref(tasklet)] = last_id
self._i += 1
if self._i % 100 == 0: #Collect at each 100 additions to the dict (no need to rush).
for tasklet_ref in list(self.tasklet_ref_to_last_id.keys()):
if tasklet_ref() is None:
del self.tasklet_ref_to_last_id[tasklet_ref]
_tasklet_to_last_id = TaskletToLastId()
#=======================================================================================================================
# _TaskletInfo
#=======================================================================================================================
class _TaskletInfo:
_last_id = 0
def __init__(self, tasklet_weakref, tasklet):
self.frame_id = None
self.tasklet_weakref = tasklet_weakref
last_id = _tasklet_to_last_id.get(tasklet)
if last_id is None:
_TaskletInfo._last_id += 1
last_id = _TaskletInfo._last_id
_tasklet_to_last_id[tasklet] = last_id
self._tasklet_id = last_id
self.update_name()
def update_name(self):
tasklet = self.tasklet_weakref()
if tasklet:
if tasklet.blocked:
state = 'blocked'
elif tasklet.paused:
state = 'paused'
elif tasklet.scheduled:
state = 'scheduled'
else:
state = '<UNEXPECTED>'
try:
name = tasklet.name
except AttributeError:
if tasklet.is_main:
name = 'MainTasklet'
else:
name = 'Tasklet-%s' % (self._tasklet_id,)
thread_id = tasklet.thread_id
if thread_id != -1:
for thread in threading.enumerate():
if thread.ident == thread_id:
if thread.name:
thread_name = "of %s" % (thread.name,)
else:
thread_name = "of Thread-%s" % (thread.name or str(thread_id),)
break
else:
# should not happen.
thread_name = "of Thread-%s" % (str(thread_id),)
thread = None
else:
# tasklet is no longer bound to a thread, because its thread ended
thread_name = "without thread"
tid = id(tasklet)
tasklet = None
else:
state = 'dead'
name = 'Tasklet-%s' % (self._tasklet_id,)
thread_name = ""
tid = '-'
self.tasklet_name = '%s %s %s (%s)' % (state, name, thread_name, tid)
if not hasattr(stackless.tasklet, "trace_function"):
# bug https://bitbucket.org/stackless-dev/stackless/issue/42
# is not fixed. Stackless releases before 2014
def update_name(self):
tasklet = self.tasklet_weakref()
if tasklet:
try:
name = tasklet.name
except AttributeError:
if tasklet.is_main:
name = 'MainTasklet'
else:
name = 'Tasklet-%s' % (self._tasklet_id,)
thread_id = tasklet.thread_id
for thread in threading.enumerate():
if thread.ident == thread_id:
if thread.name:
thread_name = "of %s" % (thread.name,)
else:
thread_name = "of Thread-%s" % (thread.name or str(thread_id),)
break
else:
# should not happen.
thread_name = "of Thread-%s" % (str(thread_id),)
thread = None
tid = id(tasklet)
tasklet = None
else:
name = 'Tasklet-%s' % (self._tasklet_id,)
thread_name = ""
tid = '-'
self.tasklet_name = '%s %s (%s)' % (name, thread_name, tid)
_weak_tasklet_registered_to_info = {}
#=======================================================================================================================
# get_tasklet_info
#=======================================================================================================================
def get_tasklet_info(tasklet):
return register_tasklet_info(tasklet)
#=======================================================================================================================
# register_tasklet_info
#=======================================================================================================================
def register_tasklet_info(tasklet):
r = weakref.ref(tasklet)
info = _weak_tasklet_registered_to_info.get(r)
if info is None:
info = _weak_tasklet_registered_to_info[r] = _TaskletInfo(r, tasklet)
return info
_application_set_schedule_callback = None
#=======================================================================================================================
# _schedule_callback
#=======================================================================================================================
def _schedule_callback(prev, next):
'''
Called when a context is stopped or a new context is made runnable.
'''
try:
if not prev and not next:
return
current_frame = sys._getframe()
if next:
register_tasklet_info(next)
# Ok, making next runnable: set the tracing facility in it.
debugger = GetGlobalDebugger()
if debugger is not None:
next.trace_function = debugger.trace_dispatch
frame = next.frame
if frame is current_frame:
frame = frame.f_back
if hasattr(frame, 'f_trace'): # Note: can be None (but hasattr should cover for that too).
frame.f_trace = debugger.trace_dispatch
debugger = None
if prev:
register_tasklet_info(prev)
try:
for tasklet_ref, tasklet_info in DictItems(_weak_tasklet_registered_to_info): # Make sure it's a copy!
tasklet = tasklet_ref()
if tasklet is None or not tasklet.alive:
# Garbage-collected already!
try:
del _weak_tasklet_registered_to_info[tasklet_ref]
except KeyError:
pass
if tasklet_info.frame_id is not None:
removeCustomFrame(tasklet_info.frame_id)
else:
is_running = stackless.get_thread_info(tasklet.thread_id)[1] is tasklet
if tasklet is prev or (tasklet is not next and not is_running):
# the tasklet won't run after this scheduler action:
# - the tasklet is the previous tasklet
# - it is not the next tasklet and it is not an already running tasklet
frame = tasklet.frame
if frame is current_frame:
frame = frame.f_back
if frame is not None:
_filename, base = GetFilenameAndBase(frame)
# print >>sys.stderr, "SchedCB: %r, %d, '%s', '%s'" % (tasklet, frame.f_lineno, _filename, base)
is_file_to_ignore = DictContains(DONT_TRACE, base)
if not is_file_to_ignore:
tasklet_info.update_name()
if tasklet_info.frame_id is None:
tasklet_info.frame_id = addCustomFrame(frame, tasklet_info.tasklet_name, tasklet.thread_id)
else:
updateCustomFrame(tasklet_info.frame_id, frame, tasklet.thread_id, name=tasklet_info.tasklet_name)
elif tasklet is next or is_running:
if tasklet_info.frame_id is not None:
# Remove info about stackless suspended when it starts to run.
removeCustomFrame(tasklet_info.frame_id)
tasklet_info.frame_id = None
finally:
tasklet = None
tasklet_info = None
frame = None
except:
import traceback;traceback.print_exc()
if _application_set_schedule_callback is not None:
return _application_set_schedule_callback(prev, next)
if not hasattr(stackless.tasklet, "trace_function"):
# Older versions of Stackless, released before 2014
# This code does not work reliable! It is affected by several
# stackless bugs: Stackless issues #44, #42, #40
def _schedule_callback(prev, next):
'''
Called when a context is stopped or a new context is made runnable.
'''
try:
if not prev and not next:
return
if next:
register_tasklet_info(next)
# Ok, making next runnable: set the tracing facility in it.
debugger = GetGlobalDebugger()
if debugger is not None and next.frame:
if hasattr(next.frame, 'f_trace'):
next.frame.f_trace = debugger.trace_dispatch
debugger = None
if prev:
register_tasklet_info(prev)
try:
for tasklet_ref, tasklet_info in DictItems(_weak_tasklet_registered_to_info): # Make sure it's a copy!
tasklet = tasklet_ref()
if tasklet is None or not tasklet.alive:
# Garbage-collected already!
try:
del _weak_tasklet_registered_to_info[tasklet_ref]
except KeyError:
pass
if tasklet_info.frame_id is not None:
removeCustomFrame(tasklet_info.frame_id)
else:
if tasklet.paused or tasklet.blocked or tasklet.scheduled:
if tasklet.frame and tasklet.frame.f_back:
f_back = tasklet.frame.f_back
_filename, base = GetFilenameAndBase(f_back)
is_file_to_ignore = DictContains(DONT_TRACE, base)
if not is_file_to_ignore:
if tasklet_info.frame_id is None:
tasklet_info.frame_id = addCustomFrame(f_back, tasklet_info.tasklet_name, tasklet.thread_id)
else:
updateCustomFrame(tasklet_info.frame_id, f_back, tasklet.thread_id)
elif tasklet.is_current:
if tasklet_info.frame_id is not None:
# Remove info about stackless suspended when it starts to run.
removeCustomFrame(tasklet_info.frame_id)
tasklet_info.frame_id = None
finally:
tasklet = None
tasklet_info = None
f_back = None
except:
import traceback;traceback.print_exc()
if _application_set_schedule_callback is not None:
return _application_set_schedule_callback(prev, next)
_original_setup = stackless.tasklet.setup
#=======================================================================================================================
# setup
#=======================================================================================================================
def setup(self, *args, **kwargs):
'''
Called to run a new tasklet: rebind the creation so that we can trace it.
'''
f = self.tempval
def new_f(old_f, args, kwargs):
debugger = GetGlobalDebugger()
if debugger is not None:
SetTrace(debugger.trace_dispatch)
debugger = None
# Remove our own traces :)
self.tempval = old_f
register_tasklet_info(self)
# Hover old_f to see the stackless being created and *args and **kwargs to see its parameters.
return old_f(*args, **kwargs)
# This is the way to tell stackless that the function it should execute is our function, not the original one. Note:
# setting tempval is the same as calling bind(new_f), but it seems that there's no other way to get the currently
# bound function, so, keeping on using tempval instead of calling bind (which is actually the same thing in a better
# API).
self.tempval = new_f
return _original_setup(self, f, args, kwargs)
#=======================================================================================================================
# __call__
#=======================================================================================================================
def __call__(self, *args, **kwargs):
'''
Called to run a new tasklet: rebind the creation so that we can trace it.
'''
return setup(self, *args, **kwargs)
_original_run = stackless.run
#=======================================================================================================================
# run
#=======================================================================================================================
def run(*args, **kwargs):
debugger = GetGlobalDebugger()
if debugger is not None:
SetTrace(debugger.trace_dispatch)
debugger = None
return _original_run(*args, **kwargs)
#=======================================================================================================================
# patch_stackless
#=======================================================================================================================
def patch_stackless():
'''
This function should be called to patch the stackless module so that new tasklets are properly tracked in the
debugger.
'''
global _application_set_schedule_callback
_application_set_schedule_callback = stackless.set_schedule_callback(_schedule_callback)
def set_schedule_callback(callable):
global _application_set_schedule_callback
old = _application_set_schedule_callback
_application_set_schedule_callback = callable
return old
def get_schedule_callback():
global _application_set_schedule_callback
return _application_set_schedule_callback
set_schedule_callback.__doc__ = stackless.set_schedule_callback.__doc__
if hasattr(stackless, "get_schedule_callback"):
get_schedule_callback.__doc__ = stackless.get_schedule_callback.__doc__
stackless.set_schedule_callback = set_schedule_callback
stackless.get_schedule_callback = get_schedule_callback
if not hasattr(stackless.tasklet, "trace_function"):
# Older versions of Stackless, released before 2014
__call__.__doc__ = stackless.tasklet.__call__.__doc__
stackless.tasklet.__call__ = __call__
setup.__doc__ = stackless.tasklet.setup.__doc__
stackless.tasklet.setup = setup
run.__doc__ = stackless.run.__doc__
stackless.run = run
patch_stackless = call_only_once(patch_stackless)