From ba6f91806a747b05a422684350fca5e50539aaf6 Mon Sep 17 00:00:00 2001 From: Karan Dhaodiyal <256503836+karandhaodiyal28-hash@users.noreply.github.com> Date: Fri, 31 Jul 2026 21:06:45 +0530 Subject: [PATCH 1/2] Return the endpoint from listen() with the in-process adapter debugpy.listen(..., in_process_debug_adapter=True) returned None, while the out-of-process path and the documented API both return the (host, port) tuple. That's a problem when you pass port 0, since there's then no way to find out which port was actually chosen. Return the endpoint and set listen.called so the in-process path matches the out-of-process one. Fixes #1656. Signed-off-by: Karan Dhaodiyal <256503836+karandhaodiyal28-hash@users.noreply.github.com> --- src/debugpy/server/api.py | 3 ++- tests/debugpy/test_api.py | 18 ++++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) create mode 100644 tests/debugpy/test_api.py diff --git a/src/debugpy/server/api.py b/src/debugpy/server/api.py index a1de5874..cdd3d3ef 100644 --- a/src/debugpy/server/api.py +++ b/src/debugpy/server/api.py @@ -156,7 +156,8 @@ def listen(address, settrace_kwargs, in_process_debug_adapter=False): block_until_connected=False, **settrace_kwargs ) - return + listen.called = True + return host, port import subprocess diff --git a/tests/debugpy/test_api.py b/tests/debugpy/test_api.py new file mode 100644 index 00000000..768edb99 --- /dev/null +++ b/tests/debugpy/test_api.py @@ -0,0 +1,18 @@ +import pytest + +import debugpy.server.api as _api + + +@pytest.fixture +def no_settrace(monkeypatch): + # Avoid actually starting pydevd; we only care about listen()'s return value. + monkeypatch.setattr(_api, "_settrace", lambda **kwargs: None) + monkeypatch.setattr(_api.listen, "called", False) + + +def test_listen_in_process_returns_endpoint(no_settrace): + # Regression test for #1656: listen(..., in_process_debug_adapter=True) used + # to return None, unlike the out-of-process path which returns (host, port). + endpoint = _api.listen(("127.0.0.1", 5678), in_process_debug_adapter=True) + assert endpoint == ("127.0.0.1", 5678) + assert _api.listen.called is True From e3c1494553dc5a65fffcdd89fc5e3fba25680d9a Mon Sep 17 00:00:00 2001 From: Karan Dhaodiyal Date: Fri, 7 Aug 2026 22:38:36 +0530 Subject: [PATCH 2/2] FIX: return actual bound port for in-process adapter in debugpy.listen() --- src/debugpy/server/api.py | 12 +++++++++++- tests/debugpy/test_api.py | 39 +++++++++++++++++++++++++++++++++++++++ 2 files changed, 50 insertions(+), 1 deletion(-) diff --git a/src/debugpy/server/api.py b/src/debugpy/server/api.py index cdd3d3ef..97cb13cc 100644 --- a/src/debugpy/server/api.py +++ b/src/debugpy/server/api.py @@ -156,8 +156,18 @@ def listen(address, settrace_kwargs, in_process_debug_adapter=False): block_until_connected=False, **settrace_kwargs ) + # pydevd binds the listening socket on a background reader thread, so + # the OS-assigned port (when port=0 is passed) isn't visible from + # here directly. Wait for the socket to be ready and read the actual + # bound endpoint back from the global debugger. + pydb = get_global_debugger() + if pydb is not None: + pydb.wait_for_server_socket_ready() + actual_host, actual_port = pydb._server_socket_name + else: + actual_host, actual_port = host, port listen.called = True - return host, port + return actual_host, actual_port import subprocess diff --git a/tests/debugpy/test_api.py b/tests/debugpy/test_api.py index 768edb99..48f1b41c 100644 --- a/tests/debugpy/test_api.py +++ b/tests/debugpy/test_api.py @@ -16,3 +16,42 @@ def test_listen_in_process_returns_endpoint(no_settrace): endpoint = _api.listen(("127.0.0.1", 5678), in_process_debug_adapter=True) assert endpoint == ("127.0.0.1", 5678) assert _api.listen.called is True + + +class _FakePyDB: + """Minimal stand-in for the global pydevd debugger. + + Mirrors the bits of the real PyDB that listen()'s in-process path reads + after _settrace(): ``wait_for_server_socket_ready`` and + ``_server_socket_name`` (set by the reader thread once the listening + socket is bound). + """ + + def __init__(self, host, port): + self._server_socket_name = (host, port) + + def wait_for_server_socket_ready(self): + return None + + +@pytest.fixture +def in_process_debugger(monkeypatch): + # Stub out _settrace (don't actually start pydevd) and inject a fake + # global debugger so that listen()'s in-process path can read the + # actual bound endpoint back from it. + monkeypatch.setattr(_api, "_settrace", lambda **kwargs: None) + monkeypatch.setattr(_api.listen, "called", False) + fake = _FakePyDB("127.0.0.1", 54321) + monkeypatch.setattr(_api, "get_global_debugger", lambda: fake) + return fake + + +def test_listen_in_process_port_zero_resolves_bound_port(in_process_debugger): + # Regression test for #1656: when port=0 is passed, listen() must return + # the OS-assigned bound port rather than the literal 0. + endpoint = _api.listen(("127.0.0.1", 0), in_process_debug_adapter=True) + host, port = endpoint + assert host == "127.0.0.1" + assert isinstance(port, int) + assert port > 0 + assert port == in_process_debugger._server_socket_name[1]