Skip to content

Commit 4901248

Browse files
committed
gh-160683: Stop half-closing the remote debugging interrupt socket
1 parent 210e7ab commit 4901248

3 files changed

Lines changed: 44 additions & 1 deletion

File tree

‎Lib/pdb.py‎

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3006,7 +3006,10 @@ def listener(sock):
30063006
# Check if the interpreter is finalizing every quarter of a second.
30073007
# Clean up and exit if so.
30083008
sock.settimeout(0.25)
3009-
sock.shutdown(socket.SHUT_WR)
3009+
# Never half-close this socket: the client only writes to it,
3010+
# and a half-closed connection waits in FIN_WAIT_2, which
3011+
# Windows resets after 120 seconds, taking the interrupt
3012+
# channel of a long-idle session with it. See gh-160683.
30103013
while not shut_down.is_set():
30113014
try:
30123015
data = sock.recv(1024)

‎Lib/test/test_remote_pdb.py‎

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -835,6 +835,41 @@ def test_reading_empty_json_during_completion(self):
835835
)
836836

837837

838+
@requires_subprocess()
839+
class SignalListenerTestCase(unittest.TestCase):
840+
def test_interrupt_socket_is_not_half_closed(self):
841+
# gh-160683: half-closing leaves the connection in FIN_WAIT_2, which
842+
# Windows tears down after 120 seconds, so a session that has been
843+
# idle that long loses its interrupt channel.
844+
# The listener thread is kept alive by an atexit callback and cannot be
845+
# joined, so start it in a subprocess to leave this interpreter's
846+
# thread bookkeeping untouched.
847+
with closing(socket.socket()) as listener:
848+
listener.bind(("127.0.0.1", 0))
849+
listener.listen(1)
850+
listener.settimeout(SHORT_TIMEOUT)
851+
code = textwrap.dedent(f"""
852+
import sys
853+
from pdb import _PdbServer
854+
855+
_PdbServer._start_signal_listener({listener.getsockname()})
856+
sys.stdin.read()
857+
""")
858+
with subprocess.Popen(
859+
[sys.executable, "-c", code], stdin=subprocess.PIPE
860+
) as process:
861+
with closing(listener.accept()[0]) as conn:
862+
conn.settimeout(1.0)
863+
try:
864+
data = conn.recv(1024)
865+
except TimeoutError:
866+
pass
867+
else:
868+
self.fail(
869+
f"interrupt socket returned {data!r} instead of staying open"
870+
)
871+
872+
838873
class RemotePdbTestCase(unittest.TestCase):
839874
"""Tests for the _PdbServer class."""
840875

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Keep the interrupt channel of a remote debugging session open.
2+
3+
Half-closing it left the connection in FIN_WAIT_2, which Windows resets
4+
after 120 seconds, so an idle session silently lost the ability to
5+
interrupt the debuggee.

0 commit comments

Comments
 (0)