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

Commit

Permalink
Fix 72 break suspension policy (#673)
Browse files Browse the repository at this point in the history
* #72: Provide a way to set breakpoint suspension policy.

* Provide a CMD_GET_THREAD_STACK (#72).
  • Loading branch information
fabioz authored and karthiknadig committed Jul 23, 2018
1 parent 5db5b30 commit d997742
Show file tree
Hide file tree
Showing 19 changed files with 484 additions and 228 deletions.
50 changes: 3 additions & 47 deletions ptvsd/_vendored/pydevd/_pydev_bundle/pydev_console_utils.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@
from _pydev_imps._pydev_saved_modules import thread
from _pydevd_bundle import pydevd_vars
from _pydevd_bundle import pydevd_xml
from _pydevd_bundle.pydevd_constants import IS_JYTHON, dict_iter_items, NEXT_VALUE_SEPARATOR
from _pydevd_bundle.pydevd_constants import IS_JYTHON, dict_iter_items, NEXT_VALUE_SEPARATOR, Null

try:
import cStringIO as StringIO #may not always be available @UnusedImport
Expand All @@ -16,51 +16,6 @@
except:
import io as StringIO

# =======================================================================================================================
# Null
# =======================================================================================================================
class Null:
"""
Gotten from: http://aspn.activestate.com/ASPN/Cookbook/Python/Recipe/68205
"""

def __init__(self, *args, **kwargs):
return None

def __call__(self, *args, **kwargs):
return self

def __getattr__(self, mname):
return self

def __setattr__(self, name, value):
return self

def __delattr__(self, name):
return self

def __repr__(self):
return "<Null>"

def __str__(self):
return "Null"

def __len__(self):
return 0

def __getitem__(self):
return self

def __setitem__(self, *args, **kwargs):
pass

def write(self, *args, **kwargs):
pass

def __nonzero__(self):
return 0


# =======================================================================================================================
# BaseStdIn
# =======================================================================================================================
Expand Down Expand Up @@ -614,8 +569,9 @@ def do_connect_to_debugger():
traceback.print_exc()
sys.stderr.write('pydevd is not available, cannot connect\n', )

from _pydevd_bundle.pydevd_constants import set_thread_id
from _pydev_bundle import pydev_localhost
threading.currentThread().__pydevd_id__ = "console_main"
set_thread_id(threading.currentThread(), "console_main")

self.orig_find_frame = pydevd_vars.find_frame
pydevd_vars.find_frame = self._findFrame
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -115,13 +115,15 @@ def __init__(self):
self.pydev_func_name = '.invalid.' # Must match the type in cython
self.suspended_at_unhandled = False

def iter_frames(self, t):
def get_topmost_frame(self, thread):
'''
Gets the topmost frame for the given thread. Note that it may be None
and callers should remove the reference to the frame as soon as possible
to avoid disturbing user code.
'''
# sys._current_frames(): dictionary with thread id -> topmost frame
current_frames = _current_frames()
v = current_frames.get(t.ident)
if v is not None:
return [v]
return []
return current_frames.get(thread.ident)

def __str__(self):
return 'State:%s Stop:%s Cmd: %s Kill:%s' % (
Expand Down
130 changes: 89 additions & 41 deletions ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_comm.py
Original file line number Diff line number Diff line change
Expand Up @@ -111,6 +111,9 @@ def unquote(s):
import StringIO #@Reimport
except:
import io as StringIO

from _pydevd_bundle.pydevd_dont_trace_files import DONT_TRACE, PYDEV_FILE
get_file_type = DONT_TRACE.get


CMD_RUN = 101
Expand Down Expand Up @@ -171,6 +174,9 @@ def unquote(s):
CMD_SHOW_CYTHON_WARNING = 150
CMD_LOAD_FULL_VALUE = 151

CMD_GET_THREAD_STACK = 152
CMD_THREAD_DUMP_TO_STDERR = 153 # This is mostly for unit-tests to diagnose errors on ci.

CMD_REDIRECT_OUTPUT = 200
CMD_GET_NEXT_STATEMENT_TARGETS = 201
CMD_SET_PROJECT_ROOTS = 202
Expand Down Expand Up @@ -233,6 +239,8 @@ def unquote(s):
'149': 'CMD_PROCESS_CREATED',
'150': 'CMD_SHOW_CYTHON_WARNING',
'151': 'CMD_LOAD_FULL_VALUE',
'152': 'CMD_GET_THREAD_STACK',
'153': 'CMD_THREAD_DUMP_TO_STDERR',

'200': 'CMD_REDIRECT_OUTPUT',
'201': 'CMD_GET_NEXT_STATEMENT_TARGETS',
Expand All @@ -249,6 +257,7 @@ def unquote(s):

from _pydev_bundle._pydev_filesystem_encoding import getfilesystemencoding
file_system_encoding = getfilesystemencoding()
filesystem_encoding_is_utf8 = file_system_encoding.lower() in ('utf-8', 'utf_8', 'utf8')

#--------------------------------------------------------------------------------------------------- UTILITIES

Expand Down Expand Up @@ -637,6 +646,22 @@ def make_list_threads_message(self, seq):
except:
return self.make_error_message(seq, get_exception_traceback_str())

def make_get_thread_stack_message(self, seq, thread_id, topmost_frame):
"""Returns thread stack as XML """
try:
# If frame is None, the return is an empty frame list.
cmd_text = ['<xml><thread id="%s">' % (thread_id,)]

if topmost_frame is not None:
try:
cmd_text.append(self.make_thread_stack_str(topmost_frame))
finally:
topmost_frame = None
cmd_text.append('</thread></xml>')
return NetCommand(CMD_GET_THREAD_STACK, seq, ''.join(cmd_text))
except:
return self.make_error_message(seq, get_exception_traceback_str())

def make_variable_changed_message(self, seq, payload):
# notify debugger that value was changed successfully
return NetCommand(CMD_RETURN, seq, payload)
Expand Down Expand Up @@ -669,71 +694,94 @@ def make_thread_killed_message(self, id):
except:
return self.make_error_message(0, get_exception_traceback_str())

def make_thread_suspend_str(self, thread_id, frame, stop_reason, message, suspend_type="trace"):
""" <xml>
<thread id="id" stop_reason="reason">
<frame id="id" name="functionName " file="file" line="line">
<var variable stuffff....
</frame>
</thread>
"""
cmd_text_list = ["<xml>"]
append = cmd_text_list.append
def make_thread_stack_str(self, frame):
make_valid_xml_value = pydevd_xml.make_valid_xml_value

if message:
message = make_valid_xml_value(message)

append('<thread id="%s" stop_reason="%s" message="%s" suspend_type="%s">' % (thread_id, stop_reason, message, suspend_type))
cmd_text_list = []
append = cmd_text_list.append

curr_frame = frame
frame = None # Clear frame reference
try:
while curr_frame:
#print cmdText
my_id = id(curr_frame)
#print "id is ", my_id

if curr_frame.f_code is None:
break #Iron Python sometimes does not have it!
break # Iron Python sometimes does not have it!

my_name = curr_frame.f_code.co_name #method name (if in method) or ? if global
if my_name is None:
break #Iron Python sometimes does not have it!

#print "name is ", my_name
method_name = curr_frame.f_code.co_name # method name (if in method) or ? if global
if method_name is None:
break # Iron Python sometimes does not have it!

abs_path_real_path_and_base = get_abs_path_real_path_and_base_from_frame(curr_frame)

myFile = pydevd_file_utils.norm_file_to_client(abs_path_real_path_and_base[0])
if file_system_encoding.lower() != "utf-8" and hasattr(myFile, "decode"):
# myFile is a byte string encoded using the file system encoding
if get_file_type(abs_path_real_path_and_base[2]) == PYDEV_FILE:
# Skip pydevd files.
curr_frame = curr_frame.f_back
continue

filename_in_utf8 = pydevd_file_utils.norm_file_to_client(abs_path_real_path_and_base[0])
if not filesystem_encoding_is_utf8 and hasattr(filename_in_utf8, "decode"):
# filename_in_utf8 is a byte string encoded using the file system encoding
# convert it to utf8
myFile = myFile.decode(file_system_encoding).encode("utf-8")
filename_in_utf8 = filename_in_utf8.decode(file_system_encoding).encode("utf-8")

#print "file is ", myFile
#myFile = inspect.getsourcefile(curr_frame) or inspect.getfile(frame)
# print("file is ", filename_in_utf8)

myLine = str(curr_frame.f_lineno)
#print "line is ", myLine
lineno = str(curr_frame.f_lineno)
# print("line is ", lineno)

#the variables are all gotten 'on-demand'
#variables = pydevd_xml.frame_vars_to_xml(curr_frame.f_locals)

variables = ''
append('<frame id="%s" name="%s" ' % (my_id , make_valid_xml_value(my_name)))
append('file="%s" line="%s">' % (quote(myFile, '/>_= \t'), myLine))
append(variables)
# Note: variables are all gotten 'on-demand'.
append('<frame id="%s" name="%s" ' % (my_id , make_valid_xml_value(method_name)))
append('file="%s" line="%s">' % (quote(filename_in_utf8, '/>_= \t'), lineno))
append("</frame>")
curr_frame = curr_frame.f_back
except :
except:
traceback.print_exc()

curr_frame = None # Clear frame reference
return ''.join(cmd_text_list)

def make_thread_suspend_str(
self,
thread_id,
frame,
stop_reason=None,
message=None,
suspend_type="trace",
):
"""
:return str:
<xml>
<thread id="id" stop_reason="reason">
<frame id="id" name="functionName " file="file" line="line">
</frame>
</thread>
</xml>
"""
make_valid_xml_value = pydevd_xml.make_valid_xml_value
cmd_text_list = []
append = cmd_text_list.append

cmd_text_list.append('<xml>')
if message:
message = make_valid_xml_value(message)

append('<thread id="%s"' % (thread_id,))
if stop_reason is not None:
append(' stop_reason="%s"' % (stop_reason,))
if message is not None:
append(' message="%s"' % (message,))
if suspend_type is not None:
append(' suspend_type="%s"' % (suspend_type,))
append('>')
append(self.make_thread_stack_str(frame))
append("</thread></xml>")

return ''.join(cmd_text_list)

def make_thread_suspend_message(self, thread_id, frame, stop_reason, message, suspend_type):
try:
return NetCommand(CMD_THREAD_SUSPEND, 0, self.make_thread_suspend_str(thread_id, frame, stop_reason, message, suspend_type))
return NetCommand(CMD_THREAD_SUSPEND, 0, self.make_thread_suspend_str(
thread_id, frame, stop_reason, message, suspend_type))
except:
return self.make_error_message(0, get_exception_traceback_str())

Expand Down
37 changes: 25 additions & 12 deletions ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -156,7 +156,7 @@ def protect_libraries_from_patching():
protect_libraries_from_patching()

from _pydev_imps._pydev_saved_modules import thread
_nextThreadIdLock = thread.allocate_lock()
_thread_id_lock = thread.allocate_lock()

if IS_PY3K:

Expand Down Expand Up @@ -251,39 +251,43 @@ def get_pid():


def clear_cached_thread_id(thread):
try:
del thread.__pydevd_id__
except AttributeError:
pass
with _thread_id_lock:
try:
if thread.__pydevd_id__ != 'console_main':
# The console_main is a special thread id used in the console and its id should never be reset
# (otherwise we may no longer be able to get its variables -- see: https://www.brainwy.com/tracker/PyDev/776).
del thread.__pydevd_id__
except AttributeError:
pass


#=======================================================================================================================
# get_thread_id
#=======================================================================================================================
def get_thread_id(thread):
try:
# Fast path without getting lock.
tid = thread.__pydevd_id__
if tid is None:
# Fix for https://www.brainwy.com/tracker/PyDev/645
# if __pydevd_id__ is None, recalculate it... also, use an heuristic
# that gives us always the same id for the thread (using thread.ident or id(thread)).
raise AttributeError()
except AttributeError:
_nextThreadIdLock.acquire()
try:
with _thread_id_lock:
# We do a new check with the lock in place just to be sure that nothing changed
tid = getattr(thread, '__pydevd_id__', None)
if tid is None:
pid = get_pid()
# Note: don't use the thread ident because if we're too early in the
# thread bootstrap process, the thread id could be still unset.
tid = thread.__pydevd_id__ = 'pid_%s_id_%s' % (pid, id(thread))
finally:
_nextThreadIdLock.release()

return tid


def set_thread_id(thread, thread_id):
with _thread_id_lock:
thread.__pydevd_id__ = thread_id


#=======================================================================================================================
# Null
#=======================================================================================================================
Expand All @@ -297,6 +301,12 @@ def __init__(self, *args, **kwargs):

def __call__(self, *args, **kwargs):
return self

def __enter__(self, *args, **kwargs):
return self

def __exit__(self, *args, **kwargs):
return self

def __getattr__(self, mname):
if len(mname) > 4 and mname[:2] == '__' and mname[-2:] == '__':
Expand Down Expand Up @@ -333,6 +343,9 @@ def __nonzero__(self):

def __iter__(self):
return iter(())

# Default instance
NULL = Null()


def call_only_once(func):
Expand Down
2 changes: 2 additions & 0 deletions ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_frame.py
Original file line number Diff line number Diff line change
Expand Up @@ -406,6 +406,8 @@ def remove_return_values(self, main_debugger, frame):
# cdef bint is_line;
# cdef bint is_call;
# cdef bint is_return;
# cdef bint should_stop;
# cdef dict breakpoints_for_file;
# cdef str curr_func_name;
# cdef bint exist_result;
# cdef dict frame_skips_cache;
Expand Down
Loading

0 comments on commit d997742

Please sign in to comment.