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

Autoreload in CherryPy no longer blocked by pydevd threads. Fixes #1691 #1702

Merged
merged 4 commits into from
Aug 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/ptvsd/_vendored/pydevd/.travis_install_python_deps.sh
Original file line number Diff line number Diff line change
Expand Up @@ -34,8 +34,9 @@ fi

if [ "$PYDEVD_PYTHON_VERSION" = "3.7" ]; then
conda install --yes pyqt=5 matplotlib
# Note: track the latest django
# Note: track the latest web framework versions.
pip install "django"
pip install "cherrypy"
fi

pip install untangle
Expand Down
Original file line number Diff line number Diff line change
@@ -1,20 +1,23 @@
from _pydev_imps._pydev_saved_modules import threading

# Hack for https://www.brainwy.com/tracker/PyDev/363 (i.e.: calling isAlive() can throw AssertionError under some
# Hack for https://www.brainwy.com/tracker/PyDev/363 (i.e.: calling is_alive() can throw AssertionError under some
# circumstances).
# It is required to debug threads started by start_new_thread in Python 3.4
_temp = threading.Thread()
if hasattr(_temp, '_is_stopped'): # Python 3.x has this
if hasattr(_temp, '_is_stopped'): # Python 3.x has this

def is_thread_alive(t):
return not t._is_stopped

elif hasattr(_temp, '_Thread__stopped'): # Python 2.x has this
elif hasattr(_temp, '_Thread__stopped'): # Python 2.x has this

def is_thread_alive(t):
return not t._Thread__stopped

else:
else:

# Jython wraps a native java thread and thus only obeys the public API.
def is_thread_alive(t):
return t.isAlive()
return t.is_alive()

del _temp
10 changes: 9 additions & 1 deletion src/ptvsd/_vendored/pydevd/_pydevd_bundle/pydevd_net_command.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
from _pydevd_bundle.pydevd_constants import DebugInfoHolder, IS_PY2, \
get_global_debugger, GetGlobalDebugger, set_global_debugger # Keep for backward compatibility @UnusedImport
from _pydevd_bundle.pydevd_utils import quote_smart as quote, to_string
from _pydevd_bundle.pydevd_comm_constants import ID_TO_MEANING
from _pydevd_bundle.pydevd_comm_constants import ID_TO_MEANING, CMD_EXIT
from _pydevd_bundle.pydevd_constants import HTTP_PROTOCOL, HTTP_JSON_PROTOCOL, \
get_protocol, IS_JYTHON
import json
Expand All @@ -17,9 +17,17 @@ def send(self, *args, **kwargs):
pass


class _NullExitCommand(_NullNetCommand):

id = CMD_EXIT


# Constant meant to be passed to the writer when the command is meant to be ignored.
NULL_NET_COMMAND = _NullNetCommand()

# Exit command -- only internal (we don't want/need to send this to the IDE).
NULL_EXIT_COMMAND = _NullExitCommand()


class NetCommand:
"""
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@
CMD_RELOAD_CODE)
from _pydevd_bundle.pydevd_constants import (DebugInfoHolder, get_thread_id, IS_IRONPYTHON,
get_global_debugger, GetGlobalDebugger, set_global_debugger) # Keep for backward compatibility @UnusedImport
from _pydevd_bundle.pydevd_net_command import NetCommand, NULL_NET_COMMAND
from _pydevd_bundle.pydevd_net_command import NetCommand, NULL_NET_COMMAND, NULL_EXIT_COMMAND
from _pydevd_bundle.pydevd_utils import quote_smart as quote, get_non_pydevd_threads
from pydevd_file_utils import get_abs_path_real_path_and_base_from_frame
import pydevd_file_utils
Expand Down Expand Up @@ -449,13 +449,7 @@ def make_load_full_value_message(self, seq, payload):
return self.make_error_message(seq, get_exception_traceback_str())

def make_exit_message(self):
try:
net = NetCommand(CMD_EXIT, 0, '')

except:
net = self.make_error_message(0, get_exception_traceback_str())

return net
return NULL_EXIT_COMMAND

def make_get_next_statement_targets_message(self, seq, payload):
try:
Expand Down
1 change: 1 addition & 0 deletions src/ptvsd/_vendored/pydevd/appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -63,6 +63,7 @@ install:
- 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)
- cmd: IF "%TEST_IRONPYTHON%"=="" IF %PYTHON_FOLDER%=="C:\\Python37-x64" (%PYTHON_EXE% -m pip install cherrypy)
- cmd: IF "%TEST_IRONPYTHON%"=="" (%PYTHON_EXE% -m pip install scapy==2.4.0 --no-warn-script-location)
- cmd: "set PYTHONPATH=%PYTHONPATH%;%APPVEYOR_BUILD_FOLDER%"

Expand Down
12 changes: 9 additions & 3 deletions src/ptvsd/_vendored/pydevd/pydevd.py
Original file line number Diff line number Diff line change
Expand Up @@ -182,10 +182,10 @@ def _on_run(self):


#=======================================================================================================================
# CheckOutputThread
# CheckAliveThread
# Non-daemon thread: guarantees that all data is written even if program is finished
#=======================================================================================================================
class CheckOutputThread(PyDBDaemonThread):
class CheckAliveThread(PyDBDaemonThread):

def __init__(self, py_db):
PyDBDaemonThread.__init__(self)
Expand Down Expand Up @@ -222,6 +222,12 @@ def wait_pydb_threads_to_finish(self, timeout=0.5):
pydev_log.debug("The following pydb threads may not have finished correctly: %s",
', '.join([t.getName() for t in pydb_daemon_threads if t is not self]))

def join(self, timeout=None):
# If someone tries to join this thread, mark it to be killed.
# This is the case for CherryPy when auto-reload is turned on.
self.do_kill_pydev_thread()
PyDBDaemonThread.join(self, timeout=timeout)


class AbstractSingleNotificationBehavior(object):
'''
Expand Down Expand Up @@ -1781,7 +1787,7 @@ def _create_check_output_thread(self):
if curr_output_checker_thread is not None:
curr_output_checker_thread.do_kill_pydev_thread()

output_checker_thread = self.output_checker_thread = CheckOutputThread(self)
output_checker_thread = self.output_checker_thread = CheckAliveThread(self)
output_checker_thread.start()

def start_auxiliary_daemon_threads(self):
Expand Down
7 changes: 7 additions & 0 deletions src/ptvsd/_vendored/pydevd/tests_python/debug_constants.py
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@

TEST_DJANGO = False
TEST_FLASK = False
TEST_CHERRYPY = False

try:
import django
Expand All @@ -34,3 +35,9 @@
TEST_FLASK = True
except:
pass

try:
import cherrypy
TEST_CHERRYPY = True
except:
pass
70 changes: 5 additions & 65 deletions src/ptvsd/_vendored/pydevd/tests_python/debugger_fixtures.py
Original file line number Diff line number Diff line change
Expand Up @@ -134,38 +134,8 @@ def _ignore_stderr_line(self, line):
return False

def create_request_thread(self, url=''):
outer = self

class T(threading.Thread):

def wait_for_contents(self):
for _ in range(10):
if hasattr(self, 'contents'):
break
time.sleep(.3)
else:
raise AssertionError('Django did not return contents properly!')
return self.contents

def run(self):
try:
from urllib.request import urlopen
except ImportError:
from urllib import urlopen
for _ in range(10):
try:
stream = urlopen('http://127.0.0.1:%s%s' % (outer.flask_port, url))
contents = stream.read()
if IS_PY3K:
contents = contents.decode('utf-8')
self.contents = contents
break
except IOError:
continue

t = T()
t.daemon = True
return t
return debugger_unittest.AbstractWriterThread.create_request_thread(
self, 'http://127.0.0.1:%s%s' % (self.flask_port, url))


class AbstractWriterThreadCaseDjango(debugger_unittest.AbstractWriterThread):
Expand Down Expand Up @@ -211,39 +181,9 @@ def write_add_exception_breakpoint_django(self, exception='Exception'):
def write_remove_exception_breakpoint_django(self, exception='Exception'):
self.write('%s\t%s\t%s' % (CMD_REMOVE_DJANGO_EXCEPTION_BREAK, self.next_seq(), exception))

def create_request_thread(self, uri):
outer = self

class T(threading.Thread):

def wait_for_contents(self):
for _ in range(10):
if hasattr(self, 'contents'):
break
time.sleep(.3)
else:
raise AssertionError('Django did not return contents properly!')
return self.contents

def run(self):
try:
from urllib.request import urlopen
except ImportError:
from urllib import urlopen
for _ in range(10):
try:
stream = urlopen('http://127.0.0.1:%s/%s' % (outer.django_port, uri))
contents = stream.read()
if IS_PY3K:
contents = contents.decode('utf-8')
self.contents = contents
break
except IOError:
continue

t = T()
t.daemon = True
return t
def create_request_thread(self, url=''):
return debugger_unittest.AbstractWriterThread.create_request_thread(
self, 'http://127.0.0.1:%s/%s' % (self.django_port, url))


class DebuggerRunnerSimple(debugger_unittest.DebuggerRunner):
Expand Down
33 changes: 33 additions & 0 deletions src/ptvsd/_vendored/pydevd/tests_python/debugger_unittest.py
Original file line number Diff line number Diff line change
Expand Up @@ -1237,6 +1237,39 @@ def msg():

wait_for_condition(condition, msg, timeout=5, sleep=.5)

def create_request_thread(self, full_url):

class T(threading.Thread):

def wait_for_contents(self):
for _ in range(10):
if hasattr(self, 'contents'):
break
time.sleep(.3)
else:
raise AssertionError('Unable to get contents from server. Url: %s' % (full_url,))
return self.contents

def run(self):
try:
from urllib.request import urlopen
except ImportError:
from urllib import urlopen
for _ in range(10):
try:
stream = urlopen(full_url)
contents = stream.read()
if IS_PY3K:
contents = contents.decode('utf-8')
self.contents = contents
break
except IOError:
continue

t = T()
t.daemon = True
return t


def _get_debugger_test_file(filename):
try:
Expand Down
6 changes: 3 additions & 3 deletions src/ptvsd/_vendored/pydevd/tests_python/test_debugger.py
Original file line number Diff line number Diff line change
Expand Up @@ -2324,7 +2324,7 @@ def run(self):
writer.write_make_initial_run()
hit2 = writer.wait_for_breakpoint_hit()
secondary_process_thread_communication.join(10)
if secondary_process_thread_communication.isAlive():
if secondary_process_thread_communication.is_alive():
raise AssertionError('The SecondaryProcessThreadCommunication did not finish')
writer.write_run_thread(hit2.thread_id)
writer.finished_ok = True
Expand Down Expand Up @@ -2391,7 +2391,7 @@ def run(self):
main_hit = writer.wait_for_breakpoint_hit(REASON_STOP_ON_BREAKPOINT)

secondary_process_thread_communication.join(10)
if secondary_process_thread_communication.isAlive():
if secondary_process_thread_communication.is_alive():
raise AssertionError('The SecondaryProcessThreadCommunication did not finish')

writer.write_run_thread(hit2.thread_id)
Expand Down Expand Up @@ -2455,7 +2455,7 @@ def run(self):
writer.write_make_initial_run()

secondary_process_thread_communication.join(10)
if secondary_process_thread_communication.isAlive():
if secondary_process_thread_communication.is_alive():
raise AssertionError('The SecondaryProcessThreadCommunication did not finish')

writer.finished_ok = True
Expand Down
Loading