10000 Allow interrupting socket reads on Windows · python/cpython@ec9d3bf · GitHub
[go: up one dir, main page]

Skip to content

Commit ec9d3bf

Browse files
committed
Allow interrupting socket reads on Windows
Since a socket read can't be directly interrupted on Windows, use the `selectors` module to watch for either new data to be read from that socket, or for a signal to arrive (leveraging `signal.set_wakeup_fd`). This allows us to watch for both types of events and handle whichever arrives first.
1 parent f65f99f commit ec9d3bf

File tree

1 file changed

+47
-6
lines changed

1 file changed

+47
-6
lines changed

Lib/pdb.py

Lines changed: 47 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -92,6 +92,7 @@
9292
import itertools
9393
import traceback
9494
import linecache
95+
import selectors
9596
import _colorize
9697

9798
from contextlib import closing
@@ -2906,6 +2907,8 @@ class _PdbClient:
29062907
def __init__(self, pid, server_socket, interrupt_script):
29072908
self.pid = pid
29082909
self.read_buf = b""
2910+
self.signal_read = None
2911+
self.signal_write = None
29092912
self.server_socket = server_socket
29102913
self.interrupt_script = interrupt_script
29112914
self.pdb_instance = Pdb()
@@ -2958,13 +2961,39 @@ def _send(self, **kwargs):
29582961
self.write_failed = True
29592962

29602963
def _readline(self):
2961-
while b"\n" not in self.read_buf:
2962-
self.read_buf += self.server_socket.recv(16 * 1024)
2963-
if not self.read_buf:
2964-
return b""
2964+
# Wait for either a SIGINT or a line or EOF from the PDB server.
2965+
selector = selectors.DefaultSelector()
2966+
selector.register(self.signal_read, selectors.EVENT_READ)
2967+
selector.register(self.server_socket, selectors.EVENT_READ)
2968+
2969+
old_wakeup_fd = signal.set_wakeup_fd(
2970+
self.signal_write.fileno(),
2971+
warn_on_full_buffer=False,
2972+
)
29652973

2966-
ret, sep, self.read_buf = self.read_buf.partition(b"\n")
2967-
return ret + sep
2974+
got_sigint = False
2975+
def sigint_handler(*args, **kwargs):
2976+
nonlocal got_sigint
2977+
got_sigint = True
2978+
2979+
old_handler = signal.signal(signal.SIGINT, sigint_handler)
2980+
try:
2981+
while b"\n" not in self.read_buf:
2982+
for key, _ in selector.select():
2983+
if key.fileobj == self.signal_read:
2984+
self.signal_read.recv(1024)
2985+
if got_sigint:
2986+
raise KeyboardInterrupt
2987+
elif key.fileobj == self.server_socket:
2988+
self.read_buf += self.server_socket.recv(16 * 1024)
2989+
if not self.read_buf:
2990+
return b""
2991+
2992+
ret, sep, self.read_buf = self.read_buf.partition(b"\n")
2993+
return ret + sep
2994+
finally:
2995+
signal.set_wakeup_fd(old_wakeup_fd)
2996+
signal.signal(signal.SIGINT, old_handler)
29682997

29692998
def read_command(self, prompt):
29702999
reply = input(prompt)
@@ -3055,9 +3084,21 @@ def _handle_sigint(self, handler):
30553084
signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGINT})
30563085
signal.signal(signal.SIGINT, old_handler)
30573086

3087+
@contextmanager
3088+
def _signal_socket_pair(self):
3089+
self.signal_read, self.signal_write = socket.socketpair()
3090+
try:
3091+
with (closing(self.signal_read), closing(self.signal_write)):
3092+
self.signal_read.setblocking(False)
3093+
self.signal_write.setblocking(False)
3094+
yield
3095+
finally:
3096+
self.signal_read = self.signal_write = None
3097+
30583098
def cmdloop(self):
30593099
with (
30603100
self._block_sigint(),
3101+
self._signal_socket_pair(),
30613102
self.readline_completion(self.complete),
30623103
):
30633104
while not self.write_failed:

0 commit comments

Comments
 (0)
0