enable_attach() no longer hangs if called from top level of an imported module on Python 2.7. Fixes #1788

This commit is contained in:
Fabio Zadrozny 2019-09-23 12:04:17 -03:00
parent 5fcc3e4cd5
commit bb5da4e19f
6 changed files with 78 additions and 55 deletions

View file

@ -115,6 +115,9 @@ except:
# CMD_XXX constants imported for backward compatibility
from _pydevd_bundle.pydevd_comm_constants import * # @UnusedWildImport
if IS_WINDOWS:
from socket import SO_EXCLUSIVEADDRUSE
if IS_JYTHON:
import org.python.core as JyCore # @UnresolvedImport
@ -187,10 +190,8 @@ def run_as_pydevd_daemon_thread(func, *args, **kwargs):
class ReaderThread(PyDBDaemonThread):
''' reader thread reads and dispatches commands in an infinite loop '''
def __init__(self, sock, py_db, terminate_on_socket_close=True):
def __init__(self, sock, py_db, PyDevJsonCommandProcessor, process_net_command, terminate_on_socket_close=True):
assert sock is not None
from _pydevd_bundle.pydevd_process_net_command_json import PyDevJsonCommandProcessor
from _pydevd_bundle.pydevd_process_net_command import process_net_command
PyDBDaemonThread.__init__(self)
self._terminate_on_socket_close = terminate_on_socket_close
@ -429,7 +430,6 @@ def create_server_socket(host, port):
try:
server = socket(AF_INET, SOCK_STREAM, IPPROTO_TCP)
if IS_WINDOWS:
from socket import SO_EXCLUSIVEADDRUSE
server.setsockopt(SOL_SOCKET, SO_EXCLUSIVEADDRUSE, 1)
else:
server.setsockopt(SOL_SOCKET, SO_REUSEADDR, 1)

View file

@ -18,6 +18,11 @@ except NameError:
import sys # Note: the sys import must be here anyways (others depend on it)
# Preload codecs to avoid imports to them later on which can potentially halt the debugger.
import codecs as _codecs
for _codec in ["ascii", "utf8", "utf-8", "latin1", "latin-1", "idna"]:
_codecs.lookup(_codec)
class DebugInfoHolder:
# we have to put it here because it can be set through the command line (so, the

View file

@ -67,6 +67,9 @@ from _pydevd_bundle.pydevd_comm import(InternalConsoleExec,
start_client, start_server, InternalGetBreakpointException, InternalSendCurrExceptionTrace,
InternalSendCurrExceptionTraceProceeded, run_as_pydevd_daemon_thread)
from _pydevd_bundle.pydevd_process_net_command_json import PyDevJsonCommandProcessor
from _pydevd_bundle.pydevd_process_net_command import process_net_command
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
@ -1127,7 +1130,13 @@ class PyDB(object):
curr_writer.do_kill_pydev_thread()
self.writer = WriterThread(sock, self, terminate_on_socket_close=terminate_on_socket_close)
self.reader = ReaderThread(sock, self, terminate_on_socket_close=terminate_on_socket_close)
self.reader = ReaderThread(
sock,
self,
PyDevJsonCommandProcessor=PyDevJsonCommandProcessor,
process_net_command=process_net_command,
terminate_on_socket_close=terminate_on_socket_close
)
self.writer.start()
self.reader.start()
@ -2568,7 +2577,13 @@ class DispatchReader(ReaderThread):
def __init__(self, dispatcher):
self.dispatcher = dispatcher
ReaderThread.__init__(self, self.dispatcher.client)
ReaderThread.__init__(
self,
self.dispatcher.client,
PyDevJsonCommandProcessor=PyDevJsonCommandProcessor,
process_net_command=process_net_command,
)
@overrides(ReaderThread._on_run)
def _on_run(self):

View file

@ -1,50 +1,3 @@
if __name__ == '__main__':
import os
import sys
import time
port = int(sys.argv[1])
root_dirname = os.path.dirname(os.path.dirname(__file__))
if root_dirname not in sys.path:
sys.path.append(root_dirname)
import pydevd
try:
pydevd._wait_for_attach() # Cannot be called before _enable_attach.
except AssertionError:
pass
else:
raise AssertionError('Expected _wait_for_attach to raise exception.')
assert sys.gettrace() is None
print('enable attach to port: %s' % (port,))
pydevd._enable_attach(('127.0.0.1', port))
pydevd._enable_attach(('127.0.0.1', port)) # no-op in practice
try:
pydevd._enable_attach(('127.0.0.1', port + 15)) # different port: raise error.
except AssertionError:
pass
else:
raise AssertionError('Expected _enable_attach to raise exception (because it is already hearing in another port).')
assert pydevd.get_global_debugger() is not None
assert sys.gettrace() is not None
a = 10 # Break 1
print('wait for attach')
pydevd._wait_for_attach()
print('finished wait for attach')
pydevd._wait_for_attach() # Should promptly return (already connected).
a = 20 # Break 2
pydevd._wait_for_attach() # As we disconnected on the 2nd break, this one should wait until a new configurationDone.
a = 20 # Break 3
while a == 20: # Pause 1
# The debugger should disconnect/reconnect, pause and then change 'a' to another value.
time.sleep(1 / 20.) # Pause 2
print('TEST SUCEEDED!')
# We want to call _enable_attach inside an import to make sure that it works properly that way.
import _debugger_case_wait_for_attach_impl

View file

@ -0,0 +1,49 @@
import os
import sys
import time
port = int(sys.argv[1])
root_dirname = os.path.dirname(os.path.dirname(__file__))
if root_dirname not in sys.path:
sys.path.append(root_dirname)
import pydevd
try:
pydevd._wait_for_attach() # Cannot be called before _enable_attach.
except AssertionError:
pass
else:
raise AssertionError('Expected _wait_for_attach to raise exception.')
assert sys.gettrace() is None
print('enable attach to port: %s' % (port,))
pydevd._enable_attach(('127.0.0.1', port))
pydevd._enable_attach(('127.0.0.1', port)) # no-op in practice
try:
pydevd._enable_attach(('127.0.0.1', port + 15)) # different port: raise error.
except AssertionError:
pass
else:
raise AssertionError('Expected _enable_attach to raise exception (because it is already hearing in another port).')
assert pydevd.get_global_debugger() is not None
assert sys.gettrace() is not None
a = 10 # Break 1
print('wait for attach')
pydevd._wait_for_attach()
print('finished wait for attach')
pydevd._wait_for_attach() # Should promptly return (already connected).
a = 20 # Break 2
pydevd._wait_for_attach() # As we disconnected on the 2nd break, this one should wait until a new configurationDone.
a = 20 # Break 3
while a == 20: # Pause 1
# The debugger should disconnect/reconnect, pause and then change 'a' to another value.
time.sleep(1 / 20.) # Pause 2
print('TEST SUCEEDED!')

View file

@ -2426,6 +2426,7 @@ def test_wait_for_attach(case_setup_remote_attach_to):
assert next(iter(process_events)).body.startMethod == start_method
with case_setup_remote_attach_to.test_file('_debugger_case_wait_for_attach.py', host_port[1]) as writer:
writer.TEST_FILE = debugger_unittest._get_debugger_test_file('_debugger_case_wait_for_attach_impl.py')
time.sleep(.5) # Give some time for it to pass the first breakpoint and wait in 'wait_for_attach'.
writer.start_socket_client(*host_port)