Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 4 additions & 1 deletion Lib/pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
35 changes: 35 additions & 0 deletions Lib/test/test_remote_pdb.py
Original file line number Diff line number Diff line change
Expand Up @@ -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."""

Expand Down
Original file line number Diff line number Diff line change
@@ -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.
Loading