Skip to content
This repository has been archived by the owner on Aug 2, 2023. It is now read-only.

Commit

Permalink
Fix for #501 and #479 (issues related to pausing a thread with debugg…
Browse files Browse the repository at this point in the history
…er.set_suspend) (#508)

* Fix issue where pydevd threads could end up being traced.

* Make sure that the stepping command is properly set when a thread is suspended. Fixes #501, #479
  • Loading branch information
fabioz authored and karthiknadig committed Jun 21, 2018
1 parent 0bffee8 commit fb27b41
Show file tree
Hide file tree
Showing 7 changed files with 92 additions and 34 deletions.
2 changes: 1 addition & 1 deletion ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -346,7 +346,7 @@ def _stop_trace(self):
disable_tracing = False

if disable_tracing:
pydevd_tracing.SetTrace(None) # no debugging on this thread
pydevd_tracing.SetTrace(None, apply_to_pydevd_thread=True) # no debugging on this thread


#=======================================================================================================================
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,7 +26,6 @@
# ELSE
# ENDIF


# Cache where we should keep that we completely skipped entering some context.
# It needs to be invalidated when:
# - Breakpoints are changed
Expand Down Expand Up @@ -79,6 +78,7 @@ def trace_dispatch(py_db, frame, event, arg):
thread = threadingCurrentThread()

if getattr(thread, 'pydev_do_not_trace', None):
SetTrace(None, apply_to_pydevd_thread=True)
return None

try:
Expand Down
39 changes: 28 additions & 11 deletions ptvsd/_vendored/pydevd/pydevd.py
Original file line number Diff line number Diff line change
Expand Up @@ -696,8 +696,14 @@ def add_break_on_exception(


def set_suspend(self, thread, stop_reason):
thread.additional_info.suspend_type = PYTHON_SUSPEND
thread.additional_info.pydev_state = STATE_SUSPEND
info = thread.additional_info
info.suspend_type = PYTHON_SUSPEND
info.pydev_state = STATE_SUSPEND
if info.pydev_step_cmd == -1:
# If the step command is not specified, set it to step into
# to make sure it'll break as soon as possible.
info.pydev_step_cmd = CMD_STEP_INTO

thread.stop_reason = stop_reason

# If conditional breakpoint raises any exception during evaluation send details to Java
Expand Down Expand Up @@ -1187,7 +1193,8 @@ def write(self, s):
if IS_PY2:
# Need s in bytes
if isinstance(s, unicode):
s = s.encode('utf-8', errors='replace')
# Note: python 2.6 does not accept the "errors" keyword.
s = s.encode('utf-8', 'replace')
else:
# Need s in str
if isinstance(s, bytes):
Expand Down Expand Up @@ -1230,6 +1237,7 @@ def settrace(
trace_only_current_thread=False,
overwrite_prev_trace=False,
patch_multiprocessing=False,
stop_at_frame=None,
):
'''Sets the tracing function with the pydev debug function and initializes needed facilities.
Expand All @@ -1253,6 +1261,9 @@ def settrace(
@param patch_multiprocessing: if True we'll patch the functions which create new processes so that launched
processes are debugged.
@param stop_at_frame: if passed it'll stop at the given frame, otherwise it'll stop in the function which
called this method.
'''
_set_trace_lock.acquire()
try:
Expand All @@ -1265,6 +1276,7 @@ def settrace(
trace_only_current_thread,
overwrite_prev_trace,
patch_multiprocessing,
stop_at_frame,
)
finally:
_set_trace_lock.release()
Expand All @@ -1282,6 +1294,7 @@ def _locked_settrace(
trace_only_current_thread,
overwrite_prev_trace,
patch_multiprocessing,
stop_at_frame,
):
if patch_multiprocessing:
try:
Expand Down Expand Up @@ -1370,16 +1383,11 @@ def _locked_settrace(
PyDBCommandThread(debugger).start()
CheckOutputThread(debugger).start()

#Suspend as the last thing after all tracing is in place.
if suspend:
debugger.set_suspend(t, CMD_THREAD_SUSPEND)


else:
# ok, we're already in debug mode, with all set, so, let's just set the break
debugger = get_global_debugger()

debugger.set_trace_for_frame_and_parents(get_frame(), False)
debugger.set_trace_for_frame_and_parents(get_frame(), also_add_to_passed_frame=False, overwrite_prev_trace=True)

t = threadingCurrentThread()
try:
Expand All @@ -1394,8 +1402,17 @@ def _locked_settrace(
# Trace future threads?
debugger.patch_threads()


if suspend:
# Suspend as the last thing after all tracing is in place.
if suspend:
if stop_at_frame is not None:
# If the step was set we have to go to run state and
# set the proper frame for it to stop.
additional_info.pydev_state = STATE_RUN
additional_info.pydev_step_cmd = CMD_STEP_OVER
additional_info.pydev_step_stop = stop_at_frame
additional_info.suspend_type = PYTHON_SUSPEND
else:
# Ask to break as soon as possible.
debugger.set_suspend(t, CMD_THREAD_SUSPEND)


Expand Down
7 changes: 4 additions & 3 deletions ptvsd/_vendored/pydevd/pydevd_tracing.py
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ def _internal_set_trace(tracing_func):
TracingFunctionHolder._original_tracing(tracing_func)


def SetTrace(tracing_func, frame_eval_func=None, dummy_tracing_func=None):
def SetTrace(tracing_func, frame_eval_func=None, dummy_tracing_func=None, apply_to_pydevd_thread=False):
if tracing_func is not None and frame_eval_func is not None:
# There is no need to set tracing function if frame evaluation is available
frame_eval_func()
Expand All @@ -82,8 +82,9 @@ def SetTrace(tracing_func, frame_eval_func=None, dummy_tracing_func=None):

current_thread = threading.currentThread()
do_not_trace_before = getattr(current_thread, 'pydev_do_not_trace', None)
if do_not_trace_before:
return
if not apply_to_pydevd_thread:
if do_not_trace_before:
return
try:
TracingFunctionHolder._lock.acquire()
current_thread.pydev_do_not_trace = True # avoid settrace reentering
Expand Down
26 changes: 26 additions & 0 deletions ptvsd/_vendored/pydevd/tests_python/_debugger_case_settrace.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
def ask_for_stop(use_back):
import pydevd
if use_back:
pydevd.settrace(stop_at_frame=sys._getframe().f_back)
else:
pydevd.settrace()
print('Will stop here if use_back==False.')


def outer_method():
ask_for_stop(True)
print('will stop here.')
ask_for_stop(False)


if __name__ == '__main__':
import os
import sys
root_dirname = os.path.dirname(os.path.dirname(__file__))

if root_dirname not in sys.path:
sys.path.append(root_dirname)

outer_method()
print('TEST SUCEEDED!')

25 changes: 25 additions & 0 deletions ptvsd/_vendored/pydevd/tests_python/test_debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -1490,6 +1490,28 @@ def run(self):

self.finished_ok = True

#=======================================================================================================================
# WriterCaseSetTrace
#======================================================================================================================
class WriterCaseSetTrace(debugger_unittest.AbstractWriterThread):

TEST_FILE = debugger_unittest._get_debugger_test_file('_debugger_case_settrace.py')

def run(self):
self.start_socket()

self.write_make_initial_run()

thread_id, frame_id, line = self.wait_for_breakpoint_hit('108', True)
assert line == 12, 'Expected return to be in line 12, was: %s' % line
self.write_run_thread(thread_id)

thread_id, frame_id, line = self.wait_for_breakpoint_hit('105', True)
assert line == 7, 'Expected return to be in line 7, was: %s' % line
self.write_run_thread(thread_id)

self.finished_ok = True

#=======================================================================================================================
# WriterThreadCaseRedirectOutput
#======================================================================================================================
Expand Down Expand Up @@ -1817,6 +1839,9 @@ def test_case_handled_exceptions2(self):
def test_case_handled_exceptions3(self):
self.check_case(WriterThreadCaseHandledExceptions3)

def test_case_settrace(self):
self.check_case(WriterCaseSetTrace)

def test_redirect_output(self):
self.check_case(WriterThreadCaseRedirectOutput)

Expand Down
25 changes: 7 additions & 18 deletions ptvsd/attach_server.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,14 @@
import threading

import pydevd
# TODO: Why import these?
from _pydevd_bundle.pydevd_custom_frames import ( # noqa
CustomFramesContainer, custom_frames_container_init,
)
from _pydevd_bundle.pydevd_additional_thread_info import (
PyDBAdditionalThreadInfo,
)
from _pydevd_bundle.pydevd_comm import (
get_global_debugger, CMD_THREAD_SUSPEND,
get_global_debugger,
)

# TODO: Why import run_module & run_file?
from ptvsd._local import run_module, run_file # noqa
from ptvsd._remote import enable_attach as ptvsd_enable_attach


DEFAULT_HOST = '0.0.0.0'
DEFAULT_PORT = 5678

Expand Down Expand Up @@ -73,7 +65,6 @@ def enable_attach(address=(DEFAULT_HOST, DEFAULT_PORT), redirect_output=True):
_attached.clear()
ptvsd_enable_attach(address, redirect_output, on_attach=_attached.set)


# TODO: Add disable_attach()?


Expand All @@ -90,11 +81,9 @@ def break_into_debugger():
if not _attached.isSet() or debugger is None:
return

thread = pydevd.threadingCurrentThread()
try:
additional_info = thread.additional_info
except AttributeError:
additional_info = PyDBAdditionalThreadInfo()
thread.additional_info = additional_info

debugger.set_suspend(thread, CMD_THREAD_SUSPEND)
import sys
pydevd.settrace(
suspend=True,
trace_only_current_thread=True,
patch_multiprocessing=False,
stop_at_frame=sys._getframe().f_back)

0 comments on commit fb27b41

Please sign in to comment.