diff --git a/Lib/pdb.py b/Lib/pdb.py index 114634a4469c131..dee854209105e71 100644 --- a/Lib/pdb.py +++ b/Lib/pdb.py @@ -3006,7 +3006,10 @@ def listener(sock): # Check if the interpreter is finalizing every quarter of a second. # Clean up and exit if so. sock.settimeout(0.25) - sock.shutdown(socket.SHUT_WR) + # Never half-close this socket: the client only writes to it, + # and a half-closed connection waits in FIN_WAIT_2, which + # Windows resets after 120 seconds, taking the interrupt + # channel of a long-idle session with it. See gh-160683. while not shut_down.is_set(): try: data = sock.recv(1024) diff --git a/Lib/test/test_remote_pdb.py b/Lib/test/test_remote_pdb.py index e5387526090536d..87dcbcdf29bc4b2 100644 --- a/Lib/test/test_remote_pdb.py +++ b/Lib/test/test_remote_pdb.py @@ -835,6 +835,41 @@ def test_reading_empty_json_during_completion(self): ) +@requires_subprocess() +class SignalListenerTestCase(unittest.TestCase): + def test_interrupt_socket_is_not_half_closed(self): + # gh-160683: half-closing leaves the connection in FIN_WAIT_2, which + # Windows tears down after 120 seconds, so a session that has been + # idle that long loses its interrupt channel. + # The listener thread is kept alive by an atexit callback and cannot be + # joined, so start it in a subprocess to leave this interpreter's + # thread bookkeeping untouched. + with closing(socket.socket()) as listener: + listener.bind(("127.0.0.1", 0)) + listener.listen(1) + listener.settimeout(SHORT_TIMEOUT) + code = textwrap.dedent(f""" + import sys + from pdb import _PdbServer + + _PdbServer._start_signal_listener({listener.getsockname()}) + sys.stdin.read() + """) + with subprocess.Popen( + [sys.executable, "-c", code], stdin=subprocess.PIPE + ) as process: + with closing(listener.accept()[0]) as conn: + conn.settimeout(1.0) + try: + data = conn.recv(1024) + except TimeoutError: + pass + else: + self.fail( + f"interrupt socket returned {data!r} instead of staying open" + ) + + class RemotePdbTestCase(unittest.TestCase): """Tests for the _PdbServer class.""" diff --git a/Misc/NEWS.d/next/Library/2026-10-26-03-58-12.gh-issue-160683.Kz3q1P.rst b/Misc/NEWS.d/next/Library/2026-10-26-03-58-12.gh-issue-160683.Kz3q1P.rst new file mode 100644 index 000000000000000..5903e0eedeb8828 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2026-10-26-03-58-12.gh-issue-160683.Kz3q1P.rst @@ -0,0 +1,5 @@ +Keep the interrupt channel of a remote debugging session open. + +Half-closing it left the connection in FIN_WAIT_2, which Windows resets +after 120 seconds, so an idle session silently lost the ability to +interrupt the debuggee.