Refactored structure to find frames. #1101 (#1107)

This commit is contained in:
Fabio Zadrozny 2019-01-21 02:49:37 -02:00 committed by Karthik Nadig
parent 4877164466
commit 944fdd9ab4
25 changed files with 784 additions and 346 deletions

View file

@ -16,7 +16,7 @@ else
fi
if [ "$PYDEVD_PYTHON_VERSION" = "2.7" ]; then
conda install --yes pyqt=4
conda install --yes pyqt=4 gevent
pip install "django>=1.7,<1.8"
pip install pathlib2
@ -28,7 +28,7 @@ if [ "$PYDEVD_PYTHON_VERSION" = "3.5" ]; then
fi
if [ "$PYDEVD_PYTHON_VERSION" = "3.6" ]; then
conda install --yes pyqt=5
conda install --yes pyqt=5 gevent
pip install "django>=2.1,<2.2"
fi

View file

@ -518,21 +518,6 @@ class BaseInterpreterInterface:
# it to run in the main thread.
self.exec_queue.put(do_change_variable)
def _findFrame(self, thread_id, frame_id):
'''
Used to show console with variables connection.
Always return a frame where the locals map to our internal namespace.
'''
VIRTUAL_FRAME_ID = "1" # matches PyStackFrameConsole.java
VIRTUAL_CONSOLE_ID = "console_main" # matches PyThreadConsole.java
if thread_id == VIRTUAL_CONSOLE_ID and frame_id == VIRTUAL_FRAME_ID:
f = FakeFrame()
f.f_globals = {} # As globals=locals here, let's simply let it empty (and save a bit of network traffic).
f.f_locals = self.get_namespace()
return f
else:
return self.orig_find_frame(thread_id, frame_id)
def connectToDebugger(self, debuggerPort, debugger_options=None):
'''
Used to show console with variables connection.
@ -569,10 +554,15 @@ class BaseInterpreterInterface:
from _pydev_bundle import pydev_localhost
set_thread_id(threading.currentThread(), "console_main")
self.orig_find_frame = pydevd_vars.find_frame
pydevd_vars.find_frame = self._findFrame
VIRTUAL_FRAME_ID = "1" # matches PyStackFrameConsole.java
VIRTUAL_CONSOLE_ID = "console_main" # matches PyThreadConsole.java
f = FakeFrame()
f.f_back = None
f.f_globals = {} # As globals=locals here, let's simply let it empty (and save a bit of network traffic).
f.f_locals = self.get_namespace()
self.debugger = pydevd.PyDB()
self.debugger.add_fake_frame(thread_id=VIRTUAL_CONSOLE_ID, frame_id=VIRTUAL_FRAME_ID, frame=f)
try:
pydevd.apply_debugger_options(debugger_options)
self.debugger.connect(pydev_localhost.get_localhost(), debuggerPort)

View file

@ -567,7 +567,7 @@ class InternalGetThreadStack(InternalThreadCommand):
frame = additional_info.get_topmost_frame(t)
try:
self._cmd = py_db.cmd_factory.make_get_thread_stack_message(
self.seq, self.thread_id, frame, must_be_suspended=not timed_out)
py_db, self.seq, self.thread_id, frame, must_be_suspended=not timed_out)
finally:
frame = None
t = None
@ -644,7 +644,7 @@ class InternalGetVariable(InternalThreadCommand):
try:
xml = StringIO.StringIO()
xml.write("<xml>")
_typeName, val_dict = pydevd_vars.resolve_compound_variable_fields(self.thread_id, self.frame_id, self.scope, self.attributes)
_typeName, val_dict = pydevd_vars.resolve_compound_variable_fields(dbg, self.thread_id, self.frame_id, self.scope, self.attributes)
if val_dict is None:
val_dict = {}
@ -686,7 +686,7 @@ class InternalGetArray(InternalThreadCommand):
def do_it(self, dbg):
try:
frame = pydevd_vars.find_frame(self.thread_id, self.frame_id)
frame = dbg.find_frame(self.thread_id, self.frame_id)
var = pydevd_vars.eval_in_context(self.name, frame.f_globals, frame.f_locals)
xml = pydevd_vars.table_like_struct_to_xml(var, self.name, self.roffset, self.coffset, self.rows, self.cols, self.format)
cmd = dbg.cmd_factory.make_get_array_message(self.sequence, xml)
@ -724,7 +724,7 @@ class InternalChangeVariable(InternalThreadCommand):
def internal_get_frame(dbg, seq, thread_id, frame_id):
''' Converts request into python variable '''
try:
frame = pydevd_vars.find_frame(thread_id, frame_id)
frame = dbg.find_frame(thread_id, frame_id)
if frame is not None:
hidden_ns = pydevconsole.get_ipython_hidden_vars()
xml = "<xml>"
@ -746,7 +746,7 @@ def internal_get_frame(dbg, seq, thread_id, frame_id):
def internal_get_next_statement_targets(dbg, seq, thread_id, frame_id):
''' gets the valid line numbers for use with set next statement '''
try:
frame = pydevd_vars.find_frame(thread_id, frame_id)
frame = dbg.find_frame(thread_id, frame_id)
if frame is not None:
code = frame.f_code
xml = "<xml>"
@ -777,7 +777,7 @@ def internal_get_next_statement_targets(dbg, seq, thread_id, frame_id):
def internal_evaluate_expression(dbg, seq, thread_id, frame_id, expression, is_exec, trim_if_too_big, attr_to_set_result):
''' gets the value of a variable '''
try:
result = pydevd_vars.evaluate_expression(thread_id, frame_id, expression, is_exec)
result = pydevd_vars.evaluate_expression(dbg, thread_id, frame_id, expression, is_exec)
if attr_to_set_result != "":
pydevd_vars.change_attr_expression(thread_id, frame_id, attr_to_set_result, expression, dbg, result)
xml = "<xml>"
@ -807,7 +807,7 @@ def internal_get_completions(dbg, seq, thread_id, frame_id, act_tok, line=-1, co
act_tok += u'.'
qualifier = token_and_qualifier[1]
frame = pydevd_vars.find_frame(thread_id, frame_id)
frame = dbg.find_frame(thread_id, frame_id)
if frame is not None:
if IS_PY2:
if not isinstance(act_tok, bytes):
@ -842,7 +842,7 @@ def internal_get_description(dbg, seq, thread_id, frame_id, expression):
''' Fetch the variable description stub from the debug console
'''
try:
frame = pydevd_vars.find_frame(thread_id, frame_id)
frame = dbg.find_frame(thread_id, frame_id)
description = pydevd_console.get_description(frame, thread_id, frame_id, expression)
description = pydevd_xml.make_valid_xml_value(quote(description, '/>_= \t'))
description_xml = '<xml><var name="" type="" value="%s"/></xml>' % description
@ -951,7 +951,7 @@ class InternalEvaluateConsoleExpression(InternalThreadCommand):
</xml>
'''
try:
frame = pydevd_vars.find_frame(self.thread_id, self.frame_id)
frame = dbg.find_frame(self.thread_id, self.frame_id)
if frame is not None:
console_message = pydevd_console.execute_console_command(
frame, self.thread_id, self.frame_id, self.line, self.buffer_output)
@ -987,7 +987,7 @@ class InternalRunCustomOperation(InternalThreadCommand):
def do_it(self, dbg):
try:
res = pydevd_vars.custom_operation(self.thread_id, self.frame_id, self.scope, self.attrs,
res = pydevd_vars.custom_operation(dbg, self.thread_id, self.frame_id, self.scope, self.attrs,
self.style, self.code_or_file, self.fnname)
resEncoded = quote_plus(res)
cmd = dbg.cmd_factory.make_custom_operation_message(self.sequence, resEncoded)
@ -1012,7 +1012,7 @@ class InternalConsoleGetCompletions(InternalThreadCommand):
''' Get completions and write back to the client
'''
try:
frame = pydevd_vars.find_frame(self.thread_id, self.frame_id)
frame = dbg.find_frame(self.thread_id, self.frame_id)
completions_xml = pydevd_console.get_completions(frame, self.act_tok)
cmd = dbg.cmd_factory.make_send_console_message(self.sequence, completions_xml)
dbg.writer.add_command(cmd)
@ -1080,7 +1080,7 @@ class InternalLoadFullValue(InternalThreadCommand):
else:
scope, attrs = (variable, None)
name = scope
var_obj = pydevd_vars.getVariable(self.thread_id, self.frame_id, scope, attrs)
var_obj = pydevd_vars.getVariable(dbg, self.thread_id, self.frame_id, scope, attrs)
var_objects.append((var_obj, name))
t = GetValueAsyncThreadDebug(dbg, self.sequence, var_objects)

View file

@ -5,13 +5,11 @@ import sys
DEBUG = False
#=======================================================================================================================
# CustomFramesContainer
#=======================================================================================================================
class CustomFramesContainer:
# Actual Values initialized later on.
custom_frames_lock = None #: :type custom_frames_lock: threading.Lock
custom_frames_lock = None # : :type custom_frames_lock: threading.Lock
custom_frames = None
@ -20,7 +18,7 @@ class CustomFramesContainer:
_py_db_command_thread_event = None
def custom_frames_container_init(): #Note: no staticmethod on jython 2.1 (so, use free-function)
def custom_frames_container_init(): # Note: no staticmethod on jython 2.1 (so, use free-function)
CustomFramesContainer.custom_frames_lock = thread.allocate_lock()
@ -37,13 +35,11 @@ def custom_frames_container_init(): #Note: no staticmethod on jython 2.1 (so, us
# when we do create the debugger.
CustomFramesContainer._py_db_command_thread_event = Null()
#Initialize it the first time (it may be reinitialized later on when dealing with a fork).
# Initialize it the first time (it may be reinitialized later on when dealing with a fork).
custom_frames_container_init()
#=======================================================================================================================
# CustomFrame
#=======================================================================================================================
class CustomFrame:
def __init__(self, name, frame, thread_id):
@ -61,73 +57,59 @@ class CustomFrame:
def add_custom_frame(frame, name, thread_id):
CustomFramesContainer.custom_frames_lock.acquire()
try:
'''
It's possible to show paused frames by adding a custom frame through this API (it's
intended to be used for coroutines, but could potentially be used for generators too).
:param frame:
The topmost frame to be shown paused when a thread with thread.ident == thread_id is paused.
:param name:
The name to be shown for the custom thread in the UI.
:param thread_id:
The thread id to which this frame is related (must match thread.ident).
:return: str
Returns the custom thread id which will be used to show the given frame paused.
'''
with CustomFramesContainer.custom_frames_lock:
curr_thread_id = get_current_thread_id(threading.currentThread())
next_id = CustomFramesContainer._next_frame_id = CustomFramesContainer._next_frame_id + 1
# Note: the frame id kept contains an id and thread information on the thread where the frame was added
# so that later on we can check if the frame is from the current thread by doing frame_id.endswith('|'+thread_id).
frame_id = '__frame__:%s|%s' % (next_id, curr_thread_id)
frame_custom_thread_id = '__frame__:%s|%s' % (next_id, curr_thread_id)
if DEBUG:
sys.stderr.write('add_custom_frame: %s (%s) %s %s\n' % (
frame_id, get_abs_path_real_path_and_base_from_frame(frame)[-1], frame.f_lineno, frame.f_code.co_name))
frame_custom_thread_id, get_abs_path_real_path_and_base_from_frame(frame)[-1], frame.f_lineno, frame.f_code.co_name))
CustomFramesContainer.custom_frames[frame_id] = CustomFrame(name, frame, thread_id)
CustomFramesContainer.custom_frames[frame_custom_thread_id] = CustomFrame(name, frame, thread_id)
CustomFramesContainer._py_db_command_thread_event.set()
return frame_id
finally:
CustomFramesContainer.custom_frames_lock.release()
return frame_custom_thread_id
addCustomFrame = add_custom_frame # Backward compatibility
def update_custom_frame(frame_id, frame, thread_id, name=None):
CustomFramesContainer.custom_frames_lock.acquire()
try:
def update_custom_frame(frame_custom_thread_id, frame, thread_id, name=None):
with CustomFramesContainer.custom_frames_lock:
if DEBUG:
sys.stderr.write('update_custom_frame: %s\n' % frame_id)
sys.stderr.write('update_custom_frame: %s\n' % frame_custom_thread_id)
try:
old = CustomFramesContainer.custom_frames[frame_id]
old = CustomFramesContainer.custom_frames[frame_custom_thread_id]
if name is not None:
old.name = name
old.mod_time += 1
old.thread_id = thread_id
except:
sys.stderr.write('Unable to get frame to replace: %s\n' % (frame_id,))
sys.stderr.write('Unable to get frame to replace: %s\n' % (frame_custom_thread_id,))
import traceback;traceback.print_exc()
CustomFramesContainer._py_db_command_thread_event.set()
finally:
CustomFramesContainer.custom_frames_lock.release()
def get_custom_frame(thread_id, frame_id):
'''
:param thread_id: This should actually be the frame_id which is returned by add_custom_frame.
:param frame_id: This is the actual id() of the frame
'''
CustomFramesContainer.custom_frames_lock.acquire()
try:
frame_id = int(frame_id)
f = CustomFramesContainer.custom_frames[thread_id].frame
while f is not None:
if id(f) == frame_id:
return f
f = f.f_back
finally:
f = None
CustomFramesContainer.custom_frames_lock.release()
def remove_custom_frame(frame_id):
CustomFramesContainer.custom_frames_lock.acquire()
try:
def remove_custom_frame(frame_custom_thread_id):
with CustomFramesContainer.custom_frames_lock:
if DEBUG:
sys.stderr.write('remove_custom_frame: %s\n' % frame_id)
CustomFramesContainer.custom_frames.pop(frame_id, None)
sys.stderr.write('remove_custom_frame: %s\n' % frame_custom_thread_id)
CustomFramesContainer.custom_frames.pop(frame_custom_thread_id, None)
CustomFramesContainer._py_db_command_thread_event.set()
finally:
CustomFramesContainer.custom_frames_lock.release()
removeCustomFrame = remove_custom_frame # Backward compatibility

View file

@ -420,16 +420,10 @@ cdef class PyDBFrame:
f = f.f_back
f = None
thread_id = get_current_thread_id(thread)
pydevd_vars.add_additional_frame_by_id(thread_id, frame_id_to_frame)
try:
main_debugger.send_caught_exception_stack(thread, arg, id(frame))
self.set_suspend(thread, CMD_STEP_CAUGHT_EXCEPTION)
self.do_wait_suspend(thread, frame, event, arg)
main_debugger.send_caught_exception_stack_proceeded(thread)
finally:
pydevd_vars.remove_additional_frame_by_id(thread_id)
main_debugger.send_caught_exception_stack(thread, arg, id(frame))
self.set_suspend(thread, CMD_STEP_CAUGHT_EXCEPTION)
self.do_wait_suspend(thread, frame, event, arg)
main_debugger.send_caught_exception_stack_proceeded(thread)
except:
traceback.print_exc()

View file

@ -64,6 +64,7 @@ DONT_TRACE = {
'pydevd.py': PYDEV_FILE,
'pydevd_additional_thread_info.py': PYDEV_FILE,
'pydevd_additional_thread_info_regular.py': PYDEV_FILE,
'pydevd_api.py': PYDEV_FILE,
'pydevd_base_schema.py': PYDEV_FILE,
'pydevd_breakpoints.py': PYDEV_FILE,
'pydevd_collect_try_except_info.py': PYDEV_FILE,
@ -90,6 +91,7 @@ DONT_TRACE = {
'pydevd_helpers.py': PYDEV_FILE,
'pydevd_import_class.py': PYDEV_FILE,
'pydevd_io.py': PYDEV_FILE,
'pydevd_json_debug_options.py': PYDEV_FILE,
'pydevd_kill_all_pydevd_threads.py': PYDEV_FILE,
'pydevd_modify_bytecode.py': PYDEV_FILE,
'pydevd_net_command.py': PYDEV_FILE,
@ -108,6 +110,7 @@ DONT_TRACE = {
'pydevd_schema_log.py': PYDEV_FILE,
'pydevd_signature.py': PYDEV_FILE,
'pydevd_stackless.py': PYDEV_FILE,
'pydevd_suspended_frames.py': PYDEV_FILE,
'pydevd_thread_wrappers.py': PYDEV_FILE,
'pydevd_trace_api.py': PYDEV_FILE,
'pydevd_trace_dispatch.py': PYDEV_FILE,

View file

@ -269,16 +269,10 @@ class PyDBFrame:
f = f.f_back
f = None
thread_id = get_current_thread_id(thread)
pydevd_vars.add_additional_frame_by_id(thread_id, frame_id_to_frame)
try:
main_debugger.send_caught_exception_stack(thread, arg, id(frame))
self.set_suspend(thread, CMD_STEP_CAUGHT_EXCEPTION)
self.do_wait_suspend(thread, frame, event, arg)
main_debugger.send_caught_exception_stack_proceeded(thread)
finally:
pydevd_vars.remove_additional_frame_by_id(thread_id)
main_debugger.send_caught_exception_stack(thread, arg, id(frame))
self.set_suspend(thread, CMD_STEP_CAUGHT_EXCEPTION)
self.do_wait_suspend(thread, frame, event, arg)
main_debugger.send_caught_exception_stack_proceeded(thread)
except:
traceback.print_exc()

View file

@ -60,7 +60,7 @@ class NetCommand:
msg = '%s\t%s\t%s\n' % (cmd_id, seq, encoded)
else:
msg = '%s\t%s\t%s\n' % (cmd_id, seq, text)
msg = '%s\t%s\t%s' % (cmd_id, seq, text)
if IS_PY2:
assert isinstance(msg, str) # i.e.: bytes

View file

@ -69,10 +69,9 @@ class NetCommandFactory:
except:
return self.make_error_message(0, get_exception_traceback_str())
def make_custom_frame_created_message(self, frameId, frameDescription):
frameDescription = pydevd_xml.make_valid_xml_value(frameDescription)
cmdText = '<xml><thread name="%s" id="%s"/></xml>' % (frameDescription, frameId)
return NetCommand(CMD_THREAD_CREATE, 0, cmdText)
def make_custom_frame_created_message(self, frame_id, frame_description):
frame_description = pydevd_xml.make_valid_xml_value(frame_description)
return NetCommand(CMD_THREAD_CREATE, 0, '<xml><thread name="%s" id="%s"/></xml>' % (frame_description, frame_id))
def make_list_threads_message(self, seq):
""" returns thread listing as XML """
@ -88,7 +87,7 @@ class NetCommandFactory:
except:
return self.make_error_message(seq, get_exception_traceback_str())
def make_get_thread_stack_message(self, seq, thread_id, topmost_frame, must_be_suspended=False):
def make_get_thread_stack_message(self, py_db, seq, thread_id, topmost_frame, must_be_suspended=False):
"""
Returns thread stack as XML.
@ -99,24 +98,21 @@ class NetCommandFactory:
cmd_text = ['<xml><thread id="%s">' % (thread_id,)]
if topmost_frame is not None:
frame_id_to_lineno = {}
try:
# Note: if we detect that we're already stopped in a given place within
# the debugger, use that stack instead of creating a new one with the
# current position (this is needed because when an uncaught exception
# is reported for a given frame we are actually stopped in a different
# place within the debugger).
frame = topmost_frame
thread_stack_str = ''
while frame is not None:
if frame.f_code.co_name == 'do_wait_suspend' and frame.f_code.co_filename.endswith('pydevd.py'):
thread_stack_str = frame.f_locals.get('thread_stack_str')
break
frame = frame.f_back
else:
# : :type suspended_frames_manager: SuspendedFramesManager
suspended_frames_manager = py_db.suspended_frames_manager
info = suspended_frames_manager.get_topmost_frame_and_frame_id_to_line(thread_id)
if info is None:
# Could not find stack of suspended frame...
if must_be_suspended:
return None
cmd_text.append(thread_stack_str or self.make_thread_stack_str(topmost_frame))
else:
# Note: we have to use the topmost frame where it was suspended (it may
# be different if it was an exception).
topmost_frame, frame_id_to_lineno = info
cmd_text.append(self.make_thread_stack_str(topmost_frame, frame_id_to_lineno))
finally:
topmost_frame = None
cmd_text.append('</thread></xml>')
@ -156,15 +152,15 @@ class NetCommandFactory:
except:
return self.make_error_message(0, get_exception_traceback_str())
def make_thread_stack_str(self, frame, frame_to_lineno=None):
def make_thread_stack_str(self, frame, frame_id_to_lineno=None):
'''
:param frame_to_lineno:
:param frame_id_to_lineno:
If available, the line number for the frame will be gotten from this dict,
otherwise frame.f_lineno will be used (needed for unhandled exceptions as
the place where we report may be different from the place where it's raised).
'''
if frame_to_lineno is None:
frame_to_lineno = {}
if frame_id_to_lineno is None:
frame_id_to_lineno = {}
make_valid_xml_value = pydevd_xml.make_valid_xml_value
cmd_text_list = []
append = cmd_text_list.append
@ -174,7 +170,7 @@ class NetCommandFactory:
try:
py_db = get_global_debugger()
while curr_frame:
my_id = id(curr_frame)
frame_id = id(curr_frame)
if curr_frame.f_code is None:
break # Iron Python sometimes does not have it!
@ -197,11 +193,11 @@ class NetCommandFactory:
# print("file is ", filename_in_utf8)
lineno = frame_to_lineno.get(curr_frame, curr_frame.f_lineno)
lineno = frame_id_to_lineno.get(frame_id, curr_frame.f_lineno)
# print("line is ", lineno)
# Note: variables are all gotten 'on-demand'.
append('<frame id="%s" name="%s" ' % (my_id , make_valid_xml_value(method_name)))
append('<frame id="%s" name="%s" ' % (frame_id , make_valid_xml_value(method_name)))
append('file="%s" line="%s">' % (quote(make_valid_xml_value(filename_in_utf8), '/>_= \t'), lineno))
append("</frame>")
curr_frame = curr_frame.f_back
@ -218,7 +214,7 @@ class NetCommandFactory:
stop_reason=None,
message=None,
suspend_type="trace",
frame_to_lineno=None
frame_id_to_lineno=None
):
"""
:return tuple(str,str):
@ -257,16 +253,16 @@ class NetCommandFactory:
if suspend_type is not None:
append(' suspend_type="%s"' % (suspend_type,))
append('>')
thread_stack_str = self.make_thread_stack_str(frame, frame_to_lineno)
thread_stack_str = self.make_thread_stack_str(frame, frame_id_to_lineno)
append(thread_stack_str)
append("</thread></xml>")
return ''.join(cmd_text_list), thread_stack_str
def make_thread_suspend_message(self, thread_id, frame, stop_reason, message, suspend_type, frame_to_lineno=None):
def make_thread_suspend_message(self, thread_id, frame, stop_reason, message, suspend_type, frame_id_to_lineno=None):
try:
thread_suspend_str, thread_stack_str = self.make_thread_suspend_str(
thread_id, frame, stop_reason, message, suspend_type, frame_to_lineno=frame_to_lineno)
thread_id, frame, stop_reason, message, suspend_type, frame_id_to_lineno=frame_id_to_lineno)
cmd = NetCommand(CMD_THREAD_SUSPEND, 0, thread_suspend_str)
cmd.thread_stack_str = thread_stack_str
cmd.thread_suspend_str = thread_suspend_str

View file

@ -0,0 +1,166 @@
from contextlib import contextmanager
import sys
from _pydev_imps._pydev_saved_modules import threading
from _pydevd_bundle.pydevd_constants import get_frame
import traceback
class _FramesTracker(object):
'''
This is a helper class to be used to track frames when a thread becomes suspended.
'''
def __init__(self, suspended_frames_manager, py_db):
self._suspended_frames_manager = suspended_frames_manager
self.py_db = py_db
self._frame_id_to_frame = {}
# Note that a given frame may appear in multiple threads when we have custom
# frames added, but as those are coroutines, this map will point to the actual
# main thread (which is the one that needs to be suspended for us to get the
# variables).
self._frame_id_to_main_thread_id = {}
# A map of the suspended thread id -> list(frames ids) -- note that
# frame ids are kept in order (the first one is the suspended frame).
self._thread_id_to_frame_ids = {}
# A map of the lines where it's suspended (needed for exceptions where the frame
# lineno is not correct).
self._frame_id_to_lineno = {}
# The main suspended thread (if this is a coroutine this isn't the id of the
# coroutine thread, it's the id of the actual suspended thread).
self._main_thread_id = None
# Helper to know if it was already untracked.
self._untracked = False
# We need to be thread-safe!
self._lock = threading.Lock()
def track(self, thread_id, frame, frame_id_to_lineno, frame_custom_thread_id=None):
'''
:param thread_id:
The thread id to be used for this frame.
:param frame:
The topmost frame which is suspended at the given thread.
:param frame_id_to_lineno:
If available, the line number for the frame will be gotten from this dict,
otherwise frame.f_lineno will be used (needed for unhandled exceptions as
the place where we report may be different from the place where it's raised).
:param frame_custom_thread_id:
If None this this is the id of the thread id for the custom frame (i.e.: coroutine).
'''
with self._lock:
coroutine_or_main_thread_id = frame_custom_thread_id or thread_id
if coroutine_or_main_thread_id in self._suspended_frames_manager.thread_id_to_tracker:
sys.stderr.write('pydevd: Something is wrong. Tracker being added twice to the same thread id.\n')
self._suspended_frames_manager.thread_id_to_tracker[coroutine_or_main_thread_id] = self
self._main_thread_id = thread_id
self._frame_id_to_lineno = frame_id_to_lineno
frame_ids_from_thread = self._thread_id_to_frame_ids.setdefault(
coroutine_or_main_thread_id, [])
while frame is not None:
frame_id = id(frame)
self._frame_id_to_frame[frame_id] = frame
frame_ids_from_thread.append(frame_id)
self._frame_id_to_main_thread_id[frame_id] = thread_id
frame = frame.f_back
def untrack_all(self):
with self._lock:
if self._untracked:
# Calling multiple times is expected for the set next statement.
return
self._untracked = True
for thread_id in self._thread_id_to_frame_ids:
self._suspended_frames_manager.thread_id_to_tracker.pop(thread_id, None)
self._frame_id_to_frame.clear()
self._frame_id_to_main_thread_id.clear()
self._thread_id_to_frame_ids.clear()
self._frame_id_to_lineno.clear()
self._main_thread_id = None
self._suspended_frames_manager = None
def get_topmost_frame_and_frame_id_to_line(self, thread_id):
with self._lock:
frame_ids = self._thread_id_to_frame_ids.get(thread_id)
if frame_ids is not None:
frame_id = frame_ids[0]
return self._frame_id_to_frame[frame_id], self._frame_id_to_lineno
def find_frame(self, thread_id, frame_id):
with self._lock:
return self._frame_id_to_frame.get(frame_id)
def create_thread_suspend_command(self, thread_id, stop_reason, message, suspend_type):
with self._lock:
frame_ids = self._thread_id_to_frame_ids[thread_id]
# First one is topmost frame suspended.
frame = self._frame_id_to_frame[frame_ids[0]]
cmd = self.py_db.cmd_factory.make_thread_suspend_message(
thread_id, frame, stop_reason, message, suspend_type, frame_id_to_lineno=self._frame_id_to_lineno)
frame = None
return cmd
class SuspendedFramesManager(object):
def __init__(self):
self._thread_id_to_fake_frames = {}
self.thread_id_to_tracker = {}
def get_topmost_frame_and_frame_id_to_line(self, thread_id):
tracker = self.thread_id_to_tracker.get(thread_id)
if tracker is None:
return None
return tracker.get_topmost_frame_and_frame_id_to_line(thread_id)
@contextmanager
def track_frames(self, py_db):
tracker = _FramesTracker(self, py_db)
try:
yield tracker
finally:
tracker.untrack_all()
def add_fake_frame(self, thread_id, frame_id, frame):
self._thread_id_to_fake_frames.setdefault(thread_id, {})[int(frame_id)] = frame
def find_frame(self, thread_id, frame_id):
try:
if frame_id == "*":
return get_frame() # any frame is specified with "*"
frame_id = int(frame_id)
fake_frames = self._thread_id_to_fake_frames.get(thread_id)
if fake_frames is not None:
frame = fake_frames.get(frame_id)
if frame is not None:
return frame
frames_tracker = self.thread_id_to_tracker.get(thread_id)
if frames_tracker is not None:
frame = frames_tracker.find_frame(thread_id, frame_id)
if frame is not None:
return frame
return None
except:
traceback.print_exc()
return None

View file

@ -4,9 +4,7 @@
import pickle
from _pydevd_bundle.pydevd_constants import get_frame, get_current_thread_id, xrange
from _pydevd_bundle.pydevd_custom_frames import get_custom_frame
from _pydevd_bundle.pydevd_xml import ExceptionOnEvaluate, get_type, var_to_xml
from _pydev_imps._pydev_saved_modules import thread
try:
from StringIO import StringIO
@ -17,20 +15,17 @@ import sys # @Reimport
from _pydev_imps._pydev_saved_modules import threading
import traceback
from _pydevd_bundle import pydevd_save_locals
from _pydev_bundle.pydev_imports import Exec, quote, execfile
from _pydev_bundle.pydev_imports import Exec, execfile
from _pydevd_bundle.pydevd_utils import to_string
SENTINEL_VALUE = []
# ------------------------------------------------------------------------------------------------------ class for errors
class VariableError(RuntimeError): pass
class VariableError(RuntimeError):
pass
class FrameNotFoundError(RuntimeError): pass
def _iter_frames(frame):
def iter_frames(frame):
while frame is not None:
yield frame
frame = frame.f_back
@ -42,118 +37,12 @@ def dump_frames(thread_id):
if thread_id != get_current_thread_id(threading.currentThread()):
raise VariableError("find_frame: must execute on same thread")
curFrame = get_frame()
for frame in _iter_frames(curFrame):
frame = get_frame()
for frame in iter_frames(frame):
sys.stdout.write('%s\n' % pickle.dumps(frame))
# ===============================================================================
# AdditionalFramesContainer
# ===============================================================================
class AdditionalFramesContainer:
lock = thread.allocate_lock()
additional_frames = {} # dict of dicts
def add_additional_frame_by_id(thread_id, frames_by_id):
AdditionalFramesContainer.additional_frames[thread_id] = frames_by_id
addAdditionalFrameById = add_additional_frame_by_id # Backward compatibility
def remove_additional_frame_by_id(thread_id):
del AdditionalFramesContainer.additional_frames[thread_id]
removeAdditionalFrameById = remove_additional_frame_by_id # Backward compatibility
def has_additional_frames_by_id(thread_id):
return thread_id in AdditionalFramesContainer.additional_frames
def get_additional_frames_by_id(thread_id):
return AdditionalFramesContainer.additional_frames.get(thread_id)
def find_frame(thread_id, frame_id):
""" returns a frame on the thread that has a given frame_id """
try:
curr_thread_id = get_current_thread_id(threading.currentThread())
if thread_id != curr_thread_id:
try:
return get_custom_frame(thread_id, frame_id) # I.e.: thread_id could be a stackless frame id + thread_id.
except:
pass
raise VariableError("find_frame: must execute on same thread (%s != %s)" % (thread_id, curr_thread_id))
lookingFor = int(frame_id)
if AdditionalFramesContainer.additional_frames:
if thread_id in AdditionalFramesContainer.additional_frames:
frame = AdditionalFramesContainer.additional_frames[thread_id].get(lookingFor)
if frame is not None:
return frame
curFrame = get_frame()
if frame_id == "*":
return curFrame # any frame is specified with "*"
frameFound = None
for frame in _iter_frames(curFrame):
if lookingFor == id(frame):
frameFound = frame
del frame
break
del frame
# Important: python can hold a reference to the frame from the current context
# if an exception is raised, so, if we don't explicitly add those deletes
# we might have those variables living much more than we'd want to.
# I.e.: sys.exc_info holding reference to frame that raises exception (so, other places
# need to call sys.exc_clear())
del curFrame
if frameFound is None:
msgFrames = ''
i = 0
for frame in _iter_frames(get_frame()):
i += 1
msgFrames += str(id(frame))
if i % 5 == 0:
msgFrames += '\n'
else:
msgFrames += ' - '
# Note: commented this error message out (it may commonly happen
# if a message asking for a frame is issued while a thread is paused
# but the thread starts running before the message is actually
# handled).
# Leaving code to uncomment during tests.
# err_msg = '''find_frame: frame not found.
# Looking for thread_id:%s, frame_id:%s
# Current thread_id:%s, available frames:
# %s\n
# ''' % (thread_id, lookingFor, curr_thread_id, msgFrames)
#
# sys.stderr.write(err_msg)
return None
return frameFound
except:
import traceback
traceback.print_exc()
return None
def getVariable(thread_id, frame_id, scope, attrs):
def getVariable(dbg, thread_id, frame_id, scope, attrs):
"""
returns the value of a variable
@ -192,7 +81,7 @@ def getVariable(thread_id, frame_id, scope, attrs):
sys.stderr.write('Unable to find object with id: %s\n' % (frame_id,))
return None
frame = find_frame(thread_id, frame_id)
frame = dbg.find_frame(thread_id, frame_id)
if frame is None:
return {}
@ -208,7 +97,7 @@ def getVariable(thread_id, frame_id, scope, attrs):
for count in xrange(len(attrList)):
if count == 0:
# An Expression can be in any scope (globals/locals), therefore it needs to evaluated as an expression
var = evaluate_expression(thread_id, frame_id, attrList[count], False)
var = evaluate_expression(dbg, thread_id, frame_id, attrList[count], False)
else:
_type, _typeName, resolver = get_type(var)
var = resolver.resolve(var, attrList[count])
@ -229,7 +118,7 @@ def getVariable(thread_id, frame_id, scope, attrs):
return var
def resolve_compound_variable_fields(thread_id, frame_id, scope, attrs):
def resolve_compound_variable_fields(dbg, thread_id, frame_id, scope, attrs):
"""
Resolve compound variable in debugger scopes by its name and attributes
@ -241,7 +130,7 @@ def resolve_compound_variable_fields(thread_id, frame_id, scope, attrs):
:return: a dictionary of variables's fields
"""
var = getVariable(thread_id, frame_id, scope, attrs)
var = getVariable(dbg, thread_id, frame_id, scope, attrs)
try:
_type, _typeName, resolver = get_type(var)
@ -291,14 +180,14 @@ def resolve_compound_var_object_fields(var, attrs):
traceback.print_exc()
def custom_operation(thread_id, frame_id, scope, attrs, style, code_or_file, operation_fn_name):
def custom_operation(dbg, thread_id, frame_id, scope, attrs, style, code_or_file, operation_fn_name):
"""
We'll execute the code_or_file and then search in the namespace the operation_fn_name to execute with the given var.
code_or_file: either some code (i.e.: from pprint import pprint) or a file to be executed.
operation_fn_name: the name of the operation to execute after the exec (i.e.: pprint)
"""
expressionValue = getVariable(thread_id, frame_id, scope, attrs)
expressionValue = getVariable(dbg, thread_id, frame_id, scope, attrs)
try:
namespace = {'__name__': '<custom_operation>'}
@ -351,11 +240,11 @@ def eval_in_context(expression, globals, locals):
return result
def evaluate_expression(thread_id, frame_id, expression, is_exec):
def evaluate_expression(dbg, thread_id, frame_id, expression, is_exec):
'''returns the result of the evaluated expression
@param is_exec: determines if we should do an exec or an eval
'''
frame = find_frame(thread_id, frame_id)
frame = dbg.find_frame(thread_id, frame_id)
if frame is None:
return
@ -394,7 +283,7 @@ def evaluate_expression(thread_id, frame_id, expression, is_exec):
def change_attr_expression(thread_id, frame_id, attr, expression, dbg, value=SENTINEL_VALUE):
'''Changes some attribute in a given frame.
'''
frame = find_frame(thread_id, frame_id)
frame = dbg.find_frame(thread_id, frame_id)
if frame is None:
return
@ -619,9 +508,9 @@ def dataframe_to_xml(df, name, roffset, coffset, rows, cols, format):
elif dtype == 'f':
fmt = '.5f'
elif dtype == 'i' or dtype == 'u':
fmt= 'd'
fmt = 'd'
else:
fmt= 's'
fmt = 's'
col_formats.append('%' + fmt)
bounds = col_bounds[col]

View file

@ -59,6 +59,7 @@ install:
- cmd: IF "%TEST_IRONPYTHON%"=="" (%PYTHON_EXE% -m pip install pytest --no-warn-script-location)
- cmd: IF "%TEST_IRONPYTHON%"=="" (%PYTHON_EXE% -m pip install pytest-xdist --no-warn-script-location)
- cmd: IF "%TEST_IRONPYTHON%"=="" (%PYTHON_EXE% -m pip install psutil)
- cmd: IF "%TEST_IRONPYTHON%"=="" (%PYTHON_EXE% -m pip install gevent --no-warn-script-location)
- cmd: IF "%TEST_IRONPYTHON%"=="" (%PYTHON_EXE% -m pip install ipython --no-warn-script-location)
- cmd: IF "%TEST_IRONPYTHON%"=="" (%PYTHON_EXE% -m pip install untangle --no-warn-script-location)
- cmd: IF "%TEST_IRONPYTHON%"=="" IF %PYTHON_FOLDER%=="C:\\Python27" (%PYTHON_EXE% -m pip install django>=1.7,<1.8)

View file

@ -283,7 +283,7 @@ def process_exec_queue(interpreter):
# Note: it'll block here until return_control returns True.
inputhook()
except:
import traceback;traceback.print_exc()
traceback.print_exc()
try:
try:
code_fragment = interpreter.exec_queue.get(block=True, timeout=1/20.) # 20 calls/second
@ -541,7 +541,7 @@ class ConsoleWriter(InteractiveInterpreter):
def console_exec(thread_id, frame_id, expression, dbg):
"""returns 'False' in case expression is partially correct
"""
frame = pydevd_vars.find_frame(thread_id, frame_id)
frame = dbg.find_frame(thread_id, frame_id)
is_multiline = expression.count('@LINE@') > 1
expression = str(expression.replace('@LINE@', '\n'))

View file

@ -27,7 +27,6 @@ from _pydev_imps._pydev_saved_modules import time
from _pydevd_bundle import pydevd_extension_utils
from _pydevd_bundle import pydevd_io, pydevd_vm_type
from _pydevd_bundle import pydevd_utils
from _pydevd_bundle import pydevd_vars
from _pydevd_bundle.pydevd_additional_thread_info import set_additional_thread_info
from _pydevd_bundle.pydevd_breakpoints import ExceptionBreakpoint, get_exception_breakpoint
from _pydevd_bundle.pydevd_comm_constants import (CMD_THREAD_SUSPEND, CMD_STEP_INTO, CMD_SET_BREAK,
@ -64,6 +63,7 @@ from _pydevd_bundle.pydevd_comm import(InternalConsoleExec,
from _pydevd_bundle.pydevd_breakpoints import stop_on_unhandled_exception
from _pydevd_bundle.pydevd_collect_try_except_info import collect_try_except_info
from _pydevd_bundle.pydevd_suspended_frames import SuspendedFramesManager
__version_info__ = (1, 3, 3)
__version_info_str__ = []
@ -358,6 +358,7 @@ class PyDB(object):
self.quitting = None
self.cmd_factory = NetCommandFactory()
self._cmd_queue = defaultdict(_queue.Queue) # Key is thread id or '*', value is Queue
self.suspended_frames_manager = SuspendedFramesManager()
self.breakpoints = {}
@ -464,6 +465,9 @@ class PyDB(object):
self._dont_trace_get_file_type = DONT_TRACE.get
self.PYDEV_FILE = PYDEV_FILE
def add_fake_frame(self, thread_id, frame_id, frame):
self.suspended_frames_manager.add_fake_frame(thread_id, frame_id, frame)
def handle_breakpoint_condition(self, info, breakpoint, new_frame):
condition = breakpoint.condition
try:
@ -822,6 +826,8 @@ class PyDB(object):
all_threads = threadingEnumerate()
program_threads_dead = []
with self._lock_running_thread_ids:
reset_cache = not self._running_thread_ids
for t in all_threads:
if getattr(t, 'is_pydev_daemon_thread', False):
pass # I.e.: skip the DummyThreads created from pydev daemon threads
@ -829,23 +835,13 @@ class PyDB(object):
pydev_log.error_once('Error in debugger: Found PyDBDaemonThread not marked with is_pydev_daemon_thread=True.\n')
elif is_thread_alive(t):
if not self._running_thread_ids:
if reset_cache:
# Fix multiprocessing debug with breakpoints in both main and child processes
# (https://youtrack.jetbrains.com/issue/PY-17092) When the new process is created, the main
# thread in the new process already has the attribute 'pydevd_id', so the new thread doesn't
# get new id with its process number and the debugger loses access to both threads.
# Therefore we should update thread_id for every main thread in the new process.
# Fix it for all existing threads.
for existing_thread in all_threads:
old_thread_id = get_thread_id(existing_thread)
clear_cached_thread_id(t)
thread_id = get_thread_id(t)
if thread_id != old_thread_id:
if pydevd_vars.has_additional_frames_by_id(old_thread_id):
frames_by_id = pydevd_vars.get_additional_frames_by_id(old_thread_id)
pydevd_vars.add_additional_frame_by_id(thread_id, frames_by_id)
clear_cached_thread_id(t)
thread_id = get_thread_id(t)
program_threads_alive[thread_id] = t
@ -1103,6 +1099,10 @@ class PyDB(object):
finally:
self._main_lock.release()
def find_frame(self, thread_id, frame_id):
""" returns a frame on the thread that has a given frame_id """
return self.suspended_frames_manager.find_frame(thread_id, frame_id)
def do_wait_suspend(self, thread, frame, event, arg, is_unhandled_exception=False): # @UnusedVariable
""" busy waits until the thread state changes to RUN
it expects thread's state as attributes of the thread.
@ -1114,8 +1114,6 @@ class PyDB(object):
"""
# print('do_wait_suspend %s %s %s %s' % (frame.f_lineno, frame.f_code.co_name, frame.f_code.co_filename, event))
self.process_internal_commands()
thread_stack_str = '' # @UnusedVariable -- this is here so that `make_get_thread_stack_message`
# can retrieve it later.
thread_id = get_current_thread_id(thread)
@ -1123,34 +1121,40 @@ class PyDB(object):
message = thread.additional_info.pydev_message
suspend_type = thread.additional_info.trace_suspend_type
thread.additional_info.trace_suspend_type = 'trace' # Reset to trace mode for next call.
frame_to_lineno = {}
frame_id_to_lineno = {}
stop_reason = thread.stop_reason
if is_unhandled_exception:
# arg must be the exception info (tuple(exc_type, exc, traceback))
tb = arg[2]
while tb is not None:
frame_to_lineno[tb.tb_frame] = tb.tb_lineno
frame_id_to_lineno[id(tb.tb_frame)] = tb.tb_lineno
tb = tb.tb_next
cmd = self.cmd_factory.make_thread_suspend_message(thread_id, frame, stop_reason, message, suspend_type, frame_to_lineno=frame_to_lineno)
frame_to_lineno.clear()
thread_stack_str = cmd.thread_stack_str # @UnusedVariable -- `make_get_thread_stack_message` uses it later.
self.writer.add_command(cmd)
with CustomFramesContainer.custom_frames_lock: # @UndefinedVariable
from_this_thread = []
with self.suspended_frames_manager.track_frames(self) as frames_tracker:
frames_tracker.track(thread_id, frame, frame_id_to_lineno)
cmd = frames_tracker.create_thread_suspend_command(thread_id, stop_reason, message, suspend_type)
self.writer.add_command(cmd)
for frame_id, custom_frame in dict_iter_items(CustomFramesContainer.custom_frames):
if custom_frame.thread_id == thread.ident:
# print >> sys.stderr, 'Frame created: ', frame_id
self.writer.add_command(self.cmd_factory.make_custom_frame_created_message(frame_id, custom_frame.name))
self.writer.add_command(self.cmd_factory.make_thread_suspend_message(frame_id, custom_frame.frame, CMD_THREAD_SUSPEND, "", suspend_type))
with CustomFramesContainer.custom_frames_lock: # @UndefinedVariable
from_this_thread = []
from_this_thread.append(frame_id)
for frame_custom_thread_id, custom_frame in dict_iter_items(CustomFramesContainer.custom_frames):
if custom_frame.thread_id == thread.ident:
frames_tracker.track(thread_id, custom_frame.frame, frame_id_to_lineno, frame_custom_thread_id=frame_custom_thread_id)
# print('Frame created as thread: %s' % (frame_custom_thread_id,))
with self._threads_suspended_single_notification.notify_thread_suspended(thread_id, stop_reason):
self._do_wait_suspend(thread, frame, event, arg, suspend_type, from_this_thread)
self.writer.add_command(self.cmd_factory.make_custom_frame_created_message(
frame_custom_thread_id, custom_frame.name))
def _do_wait_suspend(self, thread, frame, event, arg, suspend_type, from_this_thread):
self.writer.add_command(
frames_tracker.create_thread_suspend_command(frame_custom_thread_id, CMD_THREAD_SUSPEND, "", suspend_type))
from_this_thread.append(frame_custom_thread_id)
with self._threads_suspended_single_notification.notify_thread_suspended(thread_id, stop_reason):
self._do_wait_suspend(thread, frame, event, arg, suspend_type, from_this_thread, frames_tracker)
def _do_wait_suspend(self, thread, frame, event, arg, suspend_type, from_this_thread, frames_tracker):
info = thread.additional_info
if info.pydev_state == STATE_SUSPEND and not self._finish_debugging_session:
@ -1199,6 +1203,8 @@ class PyDB(object):
info.pydev_message = ''
if stop:
# Uninstall the current frames tracker before running it.
frames_tracker.untrack_all()
cmd = self.cmd_factory.make_thread_run_message(get_current_thread_id(thread), info.pydev_step_cmd)
self.writer.add_command(cmd)
info.pydev_state = STATE_SUSPEND
@ -1212,7 +1218,7 @@ class PyDB(object):
thread.stop_reason = CMD_THREAD_SUSPEND
# return to the suspend state and wait for other command (without sending any
# additional notification to the client).
self._do_wait_suspend(thread, frame, event, arg, suspend_type, from_this_thread)
self._do_wait_suspend(thread, frame, event, arg, suspend_type, from_this_thread, frames_tracker)
return
elif info.pydev_step_cmd in (CMD_STEP_RETURN, CMD_STEP_RETURN_MY_CODE):
@ -1244,23 +1250,19 @@ class PyDB(object):
with CustomFramesContainer.custom_frames_lock:
# The ones that remained on last_running must now be removed.
for frame_id in from_this_thread:
# print >> sys.stderr, 'Removing created frame: ', frame_id
# print('Removing created frame: %s' % (frame_id,))
self.writer.add_command(self.cmd_factory.make_thread_killed_message(frame_id))
def do_stop_on_unhandled_exception(self, thread, frame, frames_byid, arg):
pydev_log.debug("We are stopping in post-mortem\n")
thread_id = get_thread_id(thread)
pydevd_vars.add_additional_frame_by_id(thread_id, frames_byid)
try:
try:
add_exception_to_frame(frame, arg)
self.set_suspend(thread, CMD_ADD_EXCEPTION_BREAK)
self.do_wait_suspend(thread, frame, 'exception', arg, is_unhandled_exception=True)
except:
pydev_log.error("We've got an error while stopping in post-mortem: %s\n" % (arg[0],))
add_exception_to_frame(frame, arg)
self.set_suspend(thread, CMD_ADD_EXCEPTION_BREAK)
self.do_wait_suspend(thread, frame, 'exception', arg, is_unhandled_exception=True)
except:
pydev_log.error("We've got an error while stopping in post-mortem: %s\n" % (arg[0],))
finally:
remove_exception_from_frame(frame)
pydevd_vars.remove_additional_frame_by_id(thread_id)
frame = None
def set_trace_for_frame_and_parents(self, frame, **kwargs):
@ -1491,7 +1493,7 @@ class PyDB(object):
frame = pydevd_frame_utils.Frame(None, -1, pydevd_frame_utils.FCode("Console",
os.path.abspath(os.path.dirname(__file__))), globals, globals)
thread_id = get_current_thread_id(thread)
pydevd_vars.add_additional_frame_by_id(thread_id, {id(frame): frame})
self.add_fake_frame(thread_id, id(frame), frame)
cmd = self.cmd_factory.make_show_console_message(thread_id, frame)
self.writer.add_command(cmd)
@ -2064,7 +2066,8 @@ def main():
except:
# It's ok not having stackless there...
try:
sys.exc_clear() # the exception information should be cleaned in Python 2
if hasattr(sys, 'exc_clear'):
sys.exc_clear() # the exception information should be cleaned in Python 2
except:
pass

View file

@ -30,7 +30,7 @@ class DjangoLineBreakpoint(LineBreakpoint):
return self.file == template_frame_file and self.line == template_frame_line
def __str__(self):
return "DjangoLineBreakpoint: %s-%d" %(self.file, self.line)
return "DjangoLineBreakpoint: %s-%d" % (self.file, self.line)
def add_line_breakpoint(plugin, pydb, type, file, line, condition, expression, func_name, hit_condition=None, is_logpoint=False):
@ -149,8 +149,6 @@ def suspend_django(main_debugger, thread, frame, cmd=CMD_SET_BREAK):
if frame.f_lineno is None:
return None
pydevd_vars.add_additional_frame_by_id(get_current_thread_id(thread), {id(frame): frame})
main_debugger.set_suspend(thread, cmd)
thread.additional_info.suspend_type = DJANGO_SUSPEND
@ -356,7 +354,7 @@ def cmd_step_into(plugin, main_debugger, frame, event, args, stop_info, stop):
plugin_stop = stop_info['django_stop']
stop = stop and _is_django_resolve_call(frame.f_back) and not _is_django_context_get_call(frame)
if stop:
info.pydev_django_resolve_frame = True # we remember that we've go into python code from django rendering frame
info.pydev_django_resolve_frame = True # we remember that we've go into python code from django rendering frame
return stop, plugin_stop
@ -371,7 +369,7 @@ def cmd_step_over(plugin, main_debugger, frame, event, args, stop_info, stop):
return stop, plugin_stop
else:
if event == 'return' and info.pydev_django_resolve_frame and _is_django_resolve_call(frame.f_back):
#we return to Django suspend mode and should not stop before django rendering frame
# we return to Django suspend mode and should not stop before django rendering frame
info.pydev_step_stop = frame.f_back
info.pydev_django_resolve_frame = False
thread.additional_info.suspend_type = DJANGO_SUSPEND

View file

@ -16,7 +16,7 @@ class Jinja2LineBreakpoint(LineBreakpoint):
return self.file == template_frame_file and self.line == template_frame_line
def __str__(self):
return "Jinja2LineBreakpoint: %s-%d" %(self.file, self.line)
return "Jinja2LineBreakpoint: %s-%d" % (self.file, self.line)
def add_line_breakpoint(plugin, pydb, type, file, line, condition, expression, func_name, hit_condition=None, is_logpoint=False):
@ -73,7 +73,6 @@ def _suspend_jinja2(pydb, thread, frame, cmd=CMD_SET_BREAK, message=None):
if frame.f_lineno is None:
return None
pydevd_vars.add_additional_frame_by_id(get_current_thread_id(thread), {id(frame): frame})
pydb.set_suspend(thread, cmd)
thread.additional_info.suspend_type = JINJA2_SUSPEND
@ -112,7 +111,7 @@ class Jinja2TemplateFrame:
file_name = _get_jinja2_template_filename(frame)
self.back_context = None
if 'context' in frame.f_locals:
#sometimes we don't have 'context', e.g. in macros
# sometimes we don't have 'context', e.g. in macros
self.back_context = frame.f_locals['context']
self.f_code = FCode('template', file_name)
self.f_lineno = _get_jinja2_template_line(frame)
@ -172,7 +171,7 @@ def _is_missing(item):
return False
def _find_render_function_frame(frame):
#in order to hide internal rendering functions
# in order to hide internal rendering functions
old_frame = frame
try:
while not ('self' in frame.f_locals and frame.f_locals['self'].__class__.__name__ == 'Template' and \
@ -189,7 +188,7 @@ def _get_jinja2_template_line(frame):
if '__jinja_template__' in frame.f_globals:
_debug_info = frame.f_globals['__jinja_template__']._debug_info
if _debug_info != '':
#sometimes template contains only plain text
# sometimes template contains only plain text
debug_info = frame.f_globals['__jinja_template__'].debug_info
if debug_info is None:
@ -247,26 +246,26 @@ def cmd_step_into(plugin, pydb, frame, event, args, stop_info, stop):
stop = False
if info.pydev_call_from_jinja2 is not None:
if _is_jinja2_internal_function(frame):
#if internal Jinja2 function was called, we sould continue debugging inside template
# if internal Jinja2 function was called, we sould continue debugging inside template
info.pydev_call_from_jinja2 = None
else:
#we go into python code from Jinja2 rendering frame
# we go into python code from Jinja2 rendering frame
stop = True
if event == 'call' and _is_jinja2_context_call(frame.f_back):
#we called function from context, the next step will be in function
# we called function from context, the next step will be in function
info.pydev_call_from_jinja2 = 1
if event == 'return' and _is_jinja2_context_call(frame.f_back):
#we return from python code to Jinja2 rendering frame
# we return from python code to Jinja2 rendering frame
info.pydev_step_stop = info.pydev_call_from_jinja2
info.pydev_call_from_jinja2 = None
thread.additional_info.suspend_type = JINJA2_SUSPEND
stop = False
#print "info.pydev_call_from_jinja2", info.pydev_call_from_jinja2, "stop_info", stop_info, \
# print "info.pydev_call_from_jinja2", info.pydev_call_from_jinja2, "stop_info", stop_info, \
# "thread.additional_info.suspend_type", thread.additional_info.suspend_type
#print "event", event, "farme.locals", frame.f_locals
# print "event", event, "farme.locals", frame.f_locals
return stop, plugin_stop
@ -295,19 +294,19 @@ def cmd_step_over(plugin, pydb, frame, event, args, stop_info, stop):
return stop, plugin_stop
else:
if event == 'return' and _is_jinja2_context_call(frame.f_back):
#we return from python code to Jinja2 rendering frame
# we return from python code to Jinja2 rendering frame
info.pydev_call_from_jinja2 = None
info.pydev_call_inside_jinja2 = _find_jinja2_render_frame(frame)
thread.additional_info.suspend_type = JINJA2_SUSPEND
stop = False
return stop, plugin_stop
#print "info.pydev_call_from_jinja2", info.pydev_call_from_jinja2, "stop", stop, "jinja_stop", jinja2_stop, \
# print "info.pydev_call_from_jinja2", info.pydev_call_from_jinja2, "stop", stop, "jinja_stop", jinja2_stop, \
# "thread.additional_info.suspend_type", thread.additional_info.suspend_type
#print "event", event, "info.pydev_call_inside_jinja2", info.pydev_call_inside_jinja2
#print "frame", frame, "frame.f_back", frame.f_back, "step_stop", info.pydev_step_stop
#print "is_context_call", _is_jinja2_context_call(frame)
#print "render", _is_jinja2_render_call(frame)
#print "-------------"
# print "event", event, "info.pydev_call_inside_jinja2", info.pydev_call_inside_jinja2
# print "frame", frame, "frame.f_back", frame.f_back, "step_stop", info.pydev_step_stop
# print "is_context_call", _is_jinja2_context_call(frame)
# print "render", _is_jinja2_render_call(frame)
# print "-------------"
return stop, plugin_stop
@ -323,7 +322,7 @@ def stop(plugin, pydb, frame, event, args, stop_info, arg, step_cmd):
def get_breakpoint(plugin, pydb, pydb_frame, frame, event, args):
pydb= args[0]
pydb = args[0]
filename = args[1]
info = args[2]
new_frame = None
@ -360,7 +359,7 @@ def exception_break(plugin, pydb, pydb_frame, frame, args, arg):
if pydb.jinja2_exception_break and exception is not None:
exception_type = dict_keys(pydb.jinja2_exception_break)[0]
if exception.__name__ in ('UndefinedError', 'TemplateNotFound', 'TemplatesNotFound'):
#errors in rendering
# errors in rendering
render_frame = _find_jinja2_render_frame(frame)
if render_frame:
suspend_frame = _suspend_jinja2(pydb, thread, render_frame, CMD_ADD_EXCEPTION_BREAK, message=exception_type)
@ -371,10 +370,10 @@ def exception_break(plugin, pydb, pydb_frame, frame, args, arg):
frame = suspend_frame
return flag, frame
elif exception.__name__ in ('TemplateSyntaxError', 'TemplateAssertionError'):
#errors in compile time
# errors in compile time
name = frame.f_code.co_name
if name in ('template', 'top-level template code', '<module>') or name.startswith('block '):
#Jinja2 translates exception info and creates fake frame on his own
# Jinja2 translates exception info and creates fake frame on his own
pydb_frame.set_suspend(thread, CMD_ADD_EXCEPTION_BREAK)
add_exception_to_frame(frame, (exception, value, trace))
thread.additional_info.suspend_type = JINJA2_SUSPEND

View file

@ -14,6 +14,7 @@ if sys.version_info[0] == 2:
IS_PY2 = True
IS_PY26 = sys.version_info[:2] == (2, 6)
IS_PY27 = sys.version_info[:2] == (2, 7)
IS_PY34 = sys.version_info[:2] == (3, 4)
IS_PY36 = False
if sys.version_info[0] == 3 and sys.version_info[1] == 6:

View file

@ -1,7 +1,6 @@
from contextlib import contextmanager
import os
import threading
import time
import pytest

View file

@ -179,6 +179,7 @@ class ReaderThread(threading.Thread):
def __init__(self, sock):
threading.Thread.__init__(self)
self.name = 'Test Reader Thread'
try:
from queue import Queue
except ImportError:
@ -234,7 +235,7 @@ class ReaderThread(threading.Thread):
if IS_PY3K:
show_line = line.decode('utf-8')
print('Test Reader Thread Received %s' % (show_line,))
print('%s Received %s' % (self.name, show_line,))
if line.startswith(b'Content-Length:'):
content_len = int(line.strip().split(b':', 1)[1])
@ -331,7 +332,7 @@ class DebuggerRunner(object):
args = self.add_command_line_args(args)
if SHOW_OTHER_DEBUG_INFO:
print('executing', ' '.join(args))
print('executing: %s' % (' '.join(args),))
with self.run_process(args, writer) as dct_with_stdout_stder:
try:
@ -562,6 +563,11 @@ class AbstractWriterThread(threading.Thread):
dirname = os.path.dirname(dirname)
return os.path.abspath(os.path.join(dirname, 'pydevd.py'))
def get_pydevconsole_file(self):
dirname = os.path.dirname(__file__)
dirname = os.path.dirname(dirname)
return os.path.abspath(os.path.join(dirname, 'pydevconsole.py'))
def get_line_index_with_content(self, line_content):
'''
:return the line index which has the given content (1-based).

View file

@ -0,0 +1,23 @@
import sys
from _pydevd_bundle.pydevd_custom_frames import add_custom_frame
import threading
def call1():
add_custom_frame(sys._getframe(), 'call1', threading.current_thread().ident)
def call2():
add_custom_frame(sys._getframe(), 'call2', threading.current_thread().ident)
def call3():
add_custom_frame(sys._getframe(), 'call3', threading.current_thread().ident)
if __name__ == '__main__':
call1() # break here
call2()
call3()
print('TEST SUCEEDED')

View file

@ -0,0 +1,36 @@
#!/usr/bin/env python
from gevent import monkey, sleep, threading as gevent_threading
monkey.patch_all()
import threading
called = []
class MyGreenletThread(threading.Thread):
def run(self):
for _i in range(5):
called.append(self.name) # break here
sleep()
if __name__ == '__main__':
t1 = MyGreenletThread()
t1.name = 't1'
t2 = MyGreenletThread()
t2.name = 't2'
if hasattr(gevent_threading, 'Thread'):
# Only available in newer versions of gevent.
assert isinstance(t1, gevent_threading.Thread)
assert isinstance(t2, gevent_threading.Thread)
t1.start()
t2.start()
for t1 in (t1, t2):
t1.join()
# With gevent it's always the same (gevent coroutine support makes thread
# switching serial).
assert called == ['t1', 't1', 't2', 't1', 't2', 't1', 't2', 't1', 't2', 't2']
print('TEST SUCEEDED')

View file

@ -1,5 +1,5 @@
import time
import multiprocessing
import sys
def run(name):
@ -7,5 +7,7 @@ def run(name):
if __name__ == '__main__':
if sys.version_info[0] >= 3 and sys.platform != 'win32':
multiprocessing.set_start_method('fork')
multiprocessing.Process(target=run, args=("argument to run method",)).start()
print('TEST SUCEEDED!') # break 2 here

View file

@ -0,0 +1,30 @@
import time
import multiprocessing
import threading
import sys
import os
event = threading.Event()
class MyThread(threading.Thread):
def run(self):
_a = 10
_b = 20 # break in thread here
event.set()
_c = 20
def run_in_multiprocess():
_a = 30
_b = 40 # break in process here
if __name__ == '__main__':
MyThread().start()
event.wait()
if sys.version_info[0] >= 3 and sys.platform != 'win32':
multiprocessing.set_start_method('fork')
multiprocessing.Process(target=run_in_multiprocess, args=()).start()
print('TEST SUCEEDED!') # break in main here

View file

@ -0,0 +1,205 @@
from contextlib import contextmanager
import pytest
from _pydev_bundle.pydev_override import overrides
from tests_python.debugger_fixtures import DebuggerRunnerSimple
from tests_python.debugger_unittest import AbstractWriterThread, SHOW_OTHER_DEBUG_INFO, \
start_in_daemon_thread, wait_for_condition, IS_JYTHON
from _pydev_bundle.pydev_localhost import get_socket_names, get_socket_name
from _pydev_bundle.pydev_imports import xmlrpclib
from _pydev_bundle.pydev_imports import _queue as queue
from _pydev_bundle.pydev_imports import SimpleXMLRPCServer
import time
import socket
from tests_python.debug_constants import IS_PY2
if IS_PY2:
builtin_qualifier = "__builtin__"
else:
builtin_qualifier = "builtins"
@pytest.fixture
def console_setup():
server_queue = queue.Queue()
def notify_finished(more):
server_queue.put(('notify_finished', more))
return ''
class ConsoleRunner(DebuggerRunnerSimple):
@overrides(DebuggerRunnerSimple.add_command_line_args)
def add_command_line_args(self, args):
port, client_port = get_socket_names(2, close=True)
args.extend((
writer.get_pydevconsole_file(),
str(port[1]),
str(client_port[1])
))
self.port = port
self.client_port = client_port
server = SimpleXMLRPCServer(client_port)
server.register_function(notify_finished, "NotifyFinished")
start_in_daemon_thread(server.serve_forever, [])
self.proxy = xmlrpclib.ServerProxy("http://%s:%s/" % port)
return args
class WriterThread(AbstractWriterThread):
if IS_JYTHON:
EXPECTED_RETURNCODE = 'any'
@overrides(AbstractWriterThread.additional_output_checks)
def additional_output_checks(self, stdout, stderr):
print('output found: %s - %s' % (stdout, stderr))
@overrides(AbstractWriterThread.write_dump_threads)
def write_dump_threads(self):
pass # no-op (may be called on timeout).
def execute_line(self, command, more=False):
runner.proxy.execLine(command)
assert server_queue.get(timeout=5.) == ('notify_finished', more)
def hello(self):
def _hello():
try:
msg = runner.proxy.hello('ignored')
if msg is not None:
if isinstance(msg, (list, tuple)):
msg = next(iter(msg))
if msg.lower().startswith('hello'):
return True
except:
# That's ok, communication still not ready.
pass
return False
wait_for_condition(_hello)
def close(self):
try:
runner.proxy.close()
except:
# Ignore any errors on close.
pass
def connect_to_debugger(self, debugger_port):
runner.proxy.connectToDebugger(debugger_port)
runner = ConsoleRunner()
writer = WriterThread()
class CaseSetup(object):
@contextmanager
def check_console(
self,
**kwargs
):
for key, value in kwargs.items():
assert hasattr(WriterThread, key)
setattr(WriterThread, key, value)
self.writer = writer
args = runner.get_command_line()
args = runner.add_command_line_args(args)
if SHOW_OTHER_DEBUG_INFO:
print('executing: %s' % (' '.join(args),))
try:
with runner.run_process(args, writer) as dct_with_stdout_stder:
writer.get_stdout = lambda: ''.join(dct_with_stdout_stder['stdout'])
writer.get_stderr = lambda: ''.join(dct_with_stdout_stder['stderr'])
# Make sure communication is setup.
writer.hello()
yield writer
finally:
writer.log = []
stdout = dct_with_stdout_stder['stdout']
stderr = dct_with_stdout_stder['stderr']
writer.additional_output_checks(''.join(stdout), ''.join(stderr))
return CaseSetup()
def test_console_simple(console_setup):
with console_setup.check_console() as writer:
writer.execute_line('a = 10')
writer.execute_line('print("TEST SUCEEDED")')
writer.close()
writer.finished_ok = True
def test_console_debugger_connected(console_setup):
class _DebuggerWriterThread(AbstractWriterThread):
FORCE_KILL_PROCESS_WHEN_FINISHED_OK = True
def __init__(self):
AbstractWriterThread.__init__(self)
socket_name = get_socket_name(close=True)
server_socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
server_socket.setsockopt(socket.SOL_SOCKET, socket.SO_REUSEADDR, 1)
server_socket.bind(socket_name)
server_socket.listen(1)
self.port = socket_name[1]
self.__server_socket = server_socket
def run(self):
print('waiting for second process')
self.sock, addr = self.__server_socket.accept()
print('accepted second process')
from tests_python.debugger_unittest import ReaderThread
self.reader_thread = ReaderThread(self.sock)
self.reader_thread.start()
self._sequence = -1
# initial command is always the version
self.write_version()
self.log.append('start_socket')
self.write_make_initial_run()
time.sleep(1)
seq = self.write_list_threads()
msg = self.wait_for_list_threads(seq)
assert msg.thread['name'] == 'MainThread'
assert msg.thread['id'] == 'console_main'
self.write_get_frame('console_main', '1')
self.wait_for_vars([
[
'<var name="a" type="int" qualifier="%s" value="int: 10"' % (builtin_qualifier,),
'<var name="a" type="int" value="int', # jython
],
])
self.finished_ok = True
with console_setup.check_console() as writer:
writer.execute_line('a = 10')
debugger_writer_thread = _DebuggerWriterThread()
debugger_writer_thread.start()
writer.connect_to_debugger(debugger_writer_thread.port)
wait_for_condition(lambda: debugger_writer_thread.finished_ok)
writer.execute_line('print("TEST SUCEEDED")')
writer.close()
writer.finished_ok = True

View file

@ -17,7 +17,7 @@ from tests_python.debugger_unittest import (CMD_SET_PROPERTY_TRACE, REASON_CAUGH
IS_APPVEYOR, wait_for_condition, CMD_GET_FRAME, CMD_GET_BREAKPOINT_EXCEPTION,
CMD_THREAD_SUSPEND, CMD_STEP_OVER, REASON_STEP_OVER, CMD_THREAD_SUSPEND_SINGLE_NOTIFICATION,
CMD_THREAD_RESUME_SINGLE_NOTIFICATION, REASON_STEP_RETURN, REASON_STEP_RETURN_MY_CODE,
REASON_STEP_OVER_MY_CODE, REASON_STEP_INTO)
REASON_STEP_OVER_MY_CODE, REASON_STEP_INTO, CMD_THREAD_KILL)
from _pydevd_bundle.pydevd_constants import IS_WINDOWS
from _pydevd_bundle.pydevd_comm_constants import CMD_RELOAD_CODE
try:
@ -2096,7 +2096,7 @@ def test_debug_zip_files(case_setup, tmpdir):
@pytest.mark.skipif(not IS_CPYTHON, reason='CPython only test.')
def test_multiprocessing(case_setup_multiprocessing):
def test_multiprocessing_simple(case_setup_multiprocessing):
import threading
from tests_python.debugger_unittest import AbstractWriterThread
with case_setup_multiprocessing.test_file('_debugger_case_multiprocessing.py') as writer:
@ -2122,6 +2122,7 @@ def test_multiprocessing(case_setup_multiprocessing):
new_sock, addr = server_socket.accept()
reader_thread = ReaderThread(new_sock)
reader_thread.name = ' *** Multiprocess Reader Thread'
reader_thread.start()
writer2 = SecondaryProcessWriterThread()
@ -2147,6 +2148,76 @@ def test_multiprocessing(case_setup_multiprocessing):
writer.finished_ok = True
@pytest.mark.skipif(not IS_CPYTHON, reason='CPython only test.')
def test_multiprocessing_with_stopped_breakpoints(case_setup_multiprocessing):
import threading
from tests_python.debugger_unittest import AbstractWriterThread
with case_setup_multiprocessing.test_file('_debugger_case_multiprocessing_stopped_threads.py') as writer:
break_main_line = writer.get_line_index_with_content('break in main here')
break_thread_line = writer.get_line_index_with_content('break in thread here')
break_process_line = writer.get_line_index_with_content('break in process here')
writer.write_add_breakpoint(break_main_line)
writer.write_add_breakpoint(break_thread_line)
writer.write_add_breakpoint(break_process_line)
server_socket = writer.server_socket
class SecondaryProcessWriterThread(AbstractWriterThread):
TEST_FILE = writer.get_main_filename()
_sequence = -1
class SecondaryProcessThreadCommunication(threading.Thread):
def run(self):
from tests_python.debugger_unittest import ReaderThread
server_socket.listen(1)
self.server_socket = server_socket
new_sock, addr = server_socket.accept()
reader_thread = ReaderThread(new_sock)
reader_thread.name = ' *** Multiprocess Reader Thread'
reader_thread.start()
writer2 = SecondaryProcessWriterThread()
writer2.reader_thread = reader_thread
writer2.sock = new_sock
writer2.write_version()
writer2.write_add_breakpoint(break_main_line)
writer2.write_add_breakpoint(break_thread_line)
writer2.write_add_breakpoint(break_process_line)
writer2.write_make_initial_run()
hit = writer2.wait_for_breakpoint_hit()
writer2.write_run_thread(hit.thread_id)
secondary_process_thread_communication = SecondaryProcessThreadCommunication()
secondary_process_thread_communication.start()
writer.write_make_initial_run()
hit2 = writer.wait_for_breakpoint_hit() # Breaks in thread.
writer.write_step_over(hit2.thread_id)
hit2 = writer.wait_for_breakpoint_hit(REASON_STEP_OVER) # line == event.set()
# paused on breakpoint, will start process and pause on main thread
# in the main process too.
writer.write_step_over(hit2.thread_id)
# Note: ignore the step over hit (go only for the breakpoint hit).
main_hit = writer.wait_for_breakpoint_hit(REASON_STOP_ON_BREAKPOINT)
secondary_process_thread_communication.join(10)
if secondary_process_thread_communication.isAlive():
raise AssertionError('The SecondaryProcessThreadCommunication did not finish')
writer.write_run_thread(hit2.thread_id)
writer.write_run_thread(main_hit.thread_id)
writer.finished_ok = True
@pytest.mark.skipif(not IS_CPYTHON, reason='CPython only test.')
def test_remote_debugger_basic(case_setup_remote):
with case_setup_remote.test_file('_debugger_case_remote.py') as writer:
@ -2404,7 +2475,7 @@ a = 10 # break here
assert my_temp2.call() == 2
print('TEST SUCEEDED!')
''')
path2 = tmpdir.join('my_temp2.py')
path2.write('''
def call():
@ -2415,7 +2486,7 @@ def call():
writer.write_add_breakpoint(break_line, '')
writer.write_make_initial_run()
hit = writer.wait_for_breakpoint_hit()
path2 = tmpdir.join('my_temp2.py')
path2.write('''
def call():
@ -2428,6 +2499,56 @@ def call():
writer.finished_ok = True
@pytest.mark.skipif(IS_JYTHON, reason='Not working with Jython on ci (needs investigation).')
def test_custom_frames(case_setup):
with case_setup.test_file('_debugger_case_custom_frames.py') as writer:
writer.write_add_breakpoint(writer.get_line_index_with_content('break here'))
writer.write_make_initial_run()
hit = writer.wait_for_breakpoint_hit()
for i in range(3):
writer.write_step_over(hit.thread_id)
# Check that the frame-related threads have been killed.
for _ in range(i):
writer.wait_for_message(CMD_THREAD_KILL, expect_xml=False)
# Main thread stopped
writer.wait_for_breakpoint_hit(REASON_STEP_OVER)
# At each time we have an additional custom frame (which is shown as if it
# was a thread which is created and then suspended).
for _ in range(i):
writer.wait_for_message(CMD_THREAD_CREATE)
writer.wait_for_breakpoint_hit(REASON_THREAD_SUSPEND)
writer.write_run_thread(hit.thread_id)
# Check that the frame-related threads have been killed.
for _ in range(i):
writer.wait_for_message(CMD_THREAD_KILL, expect_xml=False)
writer.finished_ok = True
@pytest.mark.skipif((not (IS_PY36 or IS_PY27)) or IS_JYTHON, reason='Gevent only installed on Py36/Py27 for tests.')
def test_gevent(case_setup):
def get_environ(writer):
env = os.environ.copy()
env['GEVENT_SUPPORT'] = 'True'
return env
with case_setup.test_file('_debugger_case_gevent.py', get_environ=get_environ) as writer:
writer.write_add_breakpoint(writer.get_line_index_with_content('break here'))
writer.write_make_initial_run()
for _i in range(10):
hit = writer.wait_for_breakpoint_hit(name='run')
writer.write_run_thread(hit.thread_id)
writer.finished_ok = True
def test_return_value(case_setup):
with case_setup.test_file('_debugger_case_return_value.py') as writer:
break_line = writer.get_line_index_with_content('break here')