From dd96ec9394d908f472d8311a428e1b0b1926374e Mon Sep 17 00:00:00 2001 From: Luke Riddle Date: Tue, 3 Dec 2024 07:06:48 -0800 Subject: [PATCH 1/5] Properly update pydevd._settrace.called --- src/debugpy/server/api.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/debugpy/server/api.py b/src/debugpy/server/api.py index ca5371333..3218eddf4 100644 --- a/src/debugpy/server/api.py +++ b/src/debugpy/server/api.py @@ -42,7 +42,7 @@ def _settrace(*args, **kwargs): # The stdin in notification is not acted upon in debugpy, so, disable it. kwargs.setdefault("notify_stdin", False) try: - return pydevd.settrace(*args, **kwargs) + pydevd.settrace(*args, **kwargs) except Exception: raise else: From 26a64cd85c362abc85bf8c0f53e4c27f68bb990d Mon Sep 17 00:00:00 2001 From: Luke Riddle Date: Thu, 5 Dec 2024 07:58:19 -0800 Subject: [PATCH 2/5] Change _settrace.called to _listen.called --- src/debugpy/server/api.py | 24 +++++++++++----------- tests/debugpy/test_attach.py | 40 ++++++++++++++++++++++++++++++++++++ 2 files changed, 52 insertions(+), 12 deletions(-) diff --git a/src/debugpy/server/api.py b/src/debugpy/server/api.py index 3218eddf4..3b30d5d09 100644 --- a/src/debugpy/server/api.py +++ b/src/debugpy/server/api.py @@ -45,11 +45,9 @@ def _settrace(*args, **kwargs): pydevd.settrace(*args, **kwargs) except Exception: raise - else: - _settrace.called = True -_settrace.called = False +_listen.called = False def ensure_logging(): @@ -78,9 +76,6 @@ def log_to(path): def configure(properties=None, **kwargs): - if _settrace.called: - raise RuntimeError("debug adapter is already running") - ensure_logging() log.debug("configure{0!r}", (properties, kwargs)) @@ -104,9 +99,6 @@ def configure(properties=None, **kwargs): def _starts_debugging(func): def debug(address, **kwargs): - if _settrace.called: - raise RuntimeError("this process already has a debug adapter") - try: _, port = address except Exception: @@ -116,7 +108,7 @@ def debug(address, **kwargs): port.__index__() # ensure it's int-like except Exception: raise ValueError("expected port or (host, port)") - if not (0 <= port < 2 ** 16): + if not (0 <= port < 2**16): raise ValueError("invalid port number") ensure_logging() @@ -150,10 +142,14 @@ def listen(address, settrace_kwargs, in_process_debug_adapter=False): # Errors below are logged with level="info", because the caller might be catching # and handling exceptions, and we don't want to spam their stderr unnecessarily. + if _listen.called: + # Multiple calls to listen() cause the debuggee to hang + raise RuntimeError("debugpy.listen() has already been called on this process") + if in_process_debug_adapter: host, port = address log.info("Listening: pydevd without debugpy adapter: {0}:{1}", host, port) - settrace_kwargs['patch_multiprocessing'] = False + settrace_kwargs["patch_multiprocessing"] = False _settrace( host=host, port=port, @@ -218,7 +214,10 @@ def listen(address, settrace_kwargs, in_process_debug_adapter=False): try: global _adapter_process _adapter_process = subprocess.Popen( - adapter_args, close_fds=True, creationflags=creationflags, env=python_env + adapter_args, + close_fds=True, + creationflags=creationflags, + env=python_env, ) if os.name == "posix": # It's going to fork again to daemonize, so we need to wait on it to @@ -288,6 +287,7 @@ def listen(address, settrace_kwargs, in_process_debug_adapter=False): **settrace_kwargs ) log.info("pydevd is connected to adapter at {0}:{1}", server_host, server_port) + _listen.called = True return client_host, client_port diff --git a/tests/debugpy/test_attach.py b/tests/debugpy/test_attach.py index 78453bfe1..d6bcad5df 100644 --- a/tests/debugpy/test_attach.py +++ b/tests/debugpy/test_attach.py @@ -102,6 +102,46 @@ def code_to_debug(): session.request_continue() +def test_multiple_listen_raises_exception(pyfile, target): + @pyfile + def code_to_debug(): + import debuggee + import debugpy + import sys + import time + + from debuggee import backchannel + + debuggee.setup() + _, host, port = sys.argv + port = int(port) + debugpy.listen(address=(host, port)) + try: + debugpy.listen(address=(host, port)) + except RuntimeError: + backchannel.send("listen_exception") + debugpy.breakpoint() + print("break") # @breakpoint + + host, port = runners.attach_connect.host, runners.attach_connect.port + with debug.Session() as session: + backchannel = session.open_backchannel() + session.spawn_debuggee( + [ + code_to_debug, + host, + port, + ] + ) + session.wait_for_adapter_socket() + + session.expect_server_socket() + session.connect_to_adapter((host, port)) + with session.request_attach(): + pass + + assert session.backchannel.receive() == "listen_exception" + @pytest.mark.parametrize("run", runners.all_attach_connect) def test_reattach(pyfile, target, run): From 830b3069867f8e0117b159c1cb0d44bb0ce6dfa7 Mon Sep 17 00:00:00 2001 From: Luke Riddle Date: Thu, 5 Dec 2024 08:14:43 -0800 Subject: [PATCH 3/5] Remove unnecessary underscore from listen --- src/debugpy/server/api.py | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/debugpy/server/api.py b/src/debugpy/server/api.py index 3b30d5d09..4b8cf9c54 100644 --- a/src/debugpy/server/api.py +++ b/src/debugpy/server/api.py @@ -47,9 +47,6 @@ def _settrace(*args, **kwargs): raise -_listen.called = False - - def ensure_logging(): """Starts logging to log.log_dir, if it hasn't already been done.""" if ensure_logging.ensured: @@ -142,7 +139,7 @@ def listen(address, settrace_kwargs, in_process_debug_adapter=False): # Errors below are logged with level="info", because the caller might be catching # and handling exceptions, and we don't want to spam their stderr unnecessarily. - if _listen.called: + if listen.called: # Multiple calls to listen() cause the debuggee to hang raise RuntimeError("debugpy.listen() has already been called on this process") @@ -287,9 +284,11 @@ def listen(address, settrace_kwargs, in_process_debug_adapter=False): **settrace_kwargs ) log.info("pydevd is connected to adapter at {0}:{1}", server_host, server_port) - _listen.called = True + listen.called = True return client_host, client_port +listen.called = False + @_starts_debugging def connect(address, settrace_kwargs, access_token=None): From cb0ca5e11006859c75e9cbadc70bac3bc6ec9046 Mon Sep 17 00:00:00 2001 From: Luke Riddle Date: Thu, 5 Dec 2024 08:19:13 -0800 Subject: [PATCH 4/5] Fix lint issues --- tests/debugpy/test_attach.py | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/tests/debugpy/test_attach.py b/tests/debugpy/test_attach.py index d6bcad5df..0ccbdd4eb 100644 --- a/tests/debugpy/test_attach.py +++ b/tests/debugpy/test_attach.py @@ -108,7 +108,6 @@ def code_to_debug(): import debuggee import debugpy import sys - import time from debuggee import backchannel @@ -125,7 +124,7 @@ def code_to_debug(): host, port = runners.attach_connect.host, runners.attach_connect.port with debug.Session() as session: - backchannel = session.open_backchannel() + session.open_backchannel() session.spawn_debuggee( [ code_to_debug, From 1551eef7c7ed6346f15426f2224483fee74d5267 Mon Sep 17 00:00:00 2001 From: Luke Riddle Date: Fri, 6 Dec 2024 10:03:30 -0800 Subject: [PATCH 5/5] Update multiple listen test --- tests/debugpy/test_attach.py | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/tests/debugpy/test_attach.py b/tests/debugpy/test_attach.py index 0ccbdd4eb..c973b09bb 100644 --- a/tests/debugpy/test_attach.py +++ b/tests/debugpy/test_attach.py @@ -102,7 +102,7 @@ def code_to_debug(): session.request_continue() -def test_multiple_listen_raises_exception(pyfile, target): +def test_multiple_listen_raises_exception(pyfile): @pyfile def code_to_debug(): import debuggee @@ -119,12 +119,14 @@ def code_to_debug(): debugpy.listen(address=(host, port)) except RuntimeError: backchannel.send("listen_exception") + + debugpy.wait_for_client() debugpy.breakpoint() print("break") # @breakpoint host, port = runners.attach_connect.host, runners.attach_connect.port with debug.Session() as session: - session.open_backchannel() + backchannel = session.open_backchannel() session.spawn_debuggee( [ code_to_debug, @@ -132,14 +134,18 @@ def code_to_debug(): port, ] ) + session.wait_for_adapter_socket() - session.expect_server_socket() session.connect_to_adapter((host, port)) with session.request_attach(): pass - - assert session.backchannel.receive() == "listen_exception" + + session.wait_for_stop( + expected_frames=[some.dap.frame(code_to_debug, "breakpoint")] + ) + assert backchannel.receive() == "listen_exception" + session.request_continue() @pytest.mark.parametrize("run", runners.all_attach_connect)