Skip to content

Commit 42e7cec

Browse files
committed
Use a single complex signal handler for the PDB client
1 parent aafa48c commit 42e7cec

File tree

1 file changed

+81
-77
lines changed

1 file changed

+81
-77
lines changed

Lib/pdb.py

Lines changed: 81 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -2909,6 +2909,8 @@ def __init__(self, pid, server_socket, interrupt_script):
29092909
self.read_buf = b""
29102910
self.signal_read = None
29112911
self.signal_write = None
2912+
self.sigint_received = False
2913+
self.raise_on_sigint = False
29122914
self.server_socket = server_socket
29132915
self.interrupt_script = interrupt_script
29142916
self.pdb_instance = Pdb()
@@ -2961,42 +2963,39 @@ def _send(self, **kwargs):
29612963
self.write_failed = True
29622964

29632965
def _readline(self):
2966+
if self.sigint_received:
2967+
# There's a pending unhandled SIGINT. Handle it now.
2968+
self.sigint_received = False
2969+
raise KeyboardInterrupt
2970+
29642971
# Wait for either a SIGINT or a line or EOF from the PDB server.
29652972
selector = selectors.DefaultSelector()
29662973
selector.register(self.signal_read, selectors.EVENT_READ)
29672974
selector.register(self.server_socket, selectors.EVENT_READ)
29682975

2969-
old_wakeup_fd = signal.set_wakeup_fd(
2970-
self.signal_write.fileno(),
2971-
warn_on_full_buffer=False,
2972-
)
2973-
2974-
got_sigint = False
2975-
def sigint_handler(*args, **kwargs):
2976-
nonlocal got_sigint
2977-
got_sigint = True
2976+
while b"\n" not in self.read_buf:
2977+
for key, _ in selector.select():
2978+
if key.fileobj == self.signal_read:
2979+
self.signal_read.recv(1024)
2980+
if self.sigint_received:
2981+
# If not, we're reading wakeup events for sigints that
2982+
# we've previously handled, and can ignore them.
2983+
self.sigint_received = False
2984+
raise KeyboardInterrupt
2985+
elif key.fileobj == self.server_socket:
2986+
data = self.server_socket.recv(16 * 1024)
2987+
self.read_buf += data
2988+
if not data and b"\n" not in self.read_buf:
2989+
# EOF without a full final line. Drop the partial line.
2990+
self.read_buf = b""
2991+
return b""
29782992

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)
2993+
ret, sep, self.read_buf = self.read_buf.partition(b"\n")
2994+
return ret + sep
29972995

29982996
def read_command(self, prompt):
2999-
reply = input(prompt)
2997+
with self._sigint_raises_keyboard_interrupt():
2998+
reply = input(prompt)
30002999

30013000
if self.state == "dumb":
30023001
# No logic applied whatsoever, just pass the raw reply back.
@@ -3022,7 +3021,8 @@ def read_command(self, prompt):
30223021
# Otherwise, valid first line of a multi-line statement
30233022
continue_prompt = "...".ljust(len(prompt))
30243023
while codeop.compile_command(reply, "<stdin>", "single") is None:
3025-
reply += "\n" + input(continue_prompt)
3024+
with self._sigint_raises_keyboard_interrupt():
3025+
reply += "\n" + input(continue_prompt)
30263026

30273027
return prefix + reply
30283028

@@ -3047,65 +3047,71 @@ def readline_completion(self, completer):
30473047
finally:
30483048
readline.set_completer(old_completer)
30493049

3050-
if not hasattr(signal, "pthread_sigmask"):
3051-
# On Windows, we must drop signals arriving while we're ignoring them.
3052-
@contextmanager
3053-
def _block_sigint(self):
3054-
old_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
3055-
try:
3056-
yield
3057-
finally:
3058-
signal.signal(signal.SIGINT, old_handler)
3050+
@contextmanager
3051+
def _sigint_handler(self):
3052+
# Signal handling strategy:
3053+
# - When we call input() we want a SIGINT to raise KeyboardInterrupt
3054+
# - Otherwise we want to write to the wakeup FD and set a flag.
3055+
# We'll break out of select() when the wakeup FD is written to,
3056+
# and we'll check the flag whenever we're about to accept input.
3057+
def handler(signum, frame):
3058+
self.sigint_received = True
3059+
if self.raise_on_sigint:
3060+
# One-shot; don't raise again until the flag is set again.
3061+
self.raise_on_sigint = False
3062+
self.sigint_received = False
3063+
raise KeyboardInterrupt
3064+
3065+
sentinel = object()
3066+
old_handler = sentinel
3067+
old_wakeup_fd = sentinel
30593068

3060-
@contextmanager
3061-
def _handle_sigint(self, handler):
3062-
old_handler = signal.signal(signal.SIGINT, handler)
3063-
try:
3064-
yield
3065-
finally:
3066-
signal.signal(signal.SIGINT, old_handler)
3067-
else:
3068-
# On Unix, we can save them to be processed later.
3069-
@contextmanager
3070-
def _block_sigint(self):
3071-
signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGINT})
3072-
try:
3073-
yield
3074-
finally:
3075-
signal.pthread_sigmask(signal.SIG_UNBLOCK, {signal.SIGINT})
3069+
self.signal_read, self.signal_write = socket.socketpair()
3070+
with (closing(self.signal_read), closing(self.signal_write)):
3071+
self.signal_read.setblocking(False)
3072+
self.signal_write.setblocking(False)
30763073

3077-
@contextmanager
3078-
def _handle_sigint(self, handler):
3079-
old_handler = signal.signal(signal.SIGINT, handler)
30803074
try:
3081-
signal.pthread_sigmask(signal.SIG_UNBLOCK, {signal.SIGINT})
3082-
yield
3075+
old_handler = signal.signal(signal.SIGINT, handler)
3076+
3077+
try:
3078+
old_wakeup_fd = signal.set_wakeup_fd(
3079+
self.signal_write.fileno(),
3080+
warn_on_full_buffer=False,
3081+
)
3082+
yield
3083+
finally:
3084+
# Restore the old wakeup fd if we installed a new one
3085+
if old_wakeup_fd is not sentinel:
3086+
signal.set_wakeup_fd(old_wakeup_fd)
3087+
self.signal_read = self.signal_write = None
30833088
finally:
3084-
signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGINT})
3085-
signal.signal(signal.SIGINT, old_handler)
3089+
if old_handler is not sentinel:
3090+
# Restore the old handler if we installed a new one
3091+
signal.signal(signal.SIGINT, old_handler)
30863092

30873093
@contextmanager
3088-
def _signal_socket_pair(self):
3089-
self.signal_read, self.signal_write = socket.socketpair()
3094+
def _sigint_raises_keyboard_interrupt(self):
3095+
if self.sigint_received:
3096+
# There's a pending unhandled SIGINT. Handle it now.
3097+
self.sigint_received = False
3098+
raise KeyboardInterrupt
3099+
30903100
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
3101+
self.raise_on_sigint = True
3102+
yield
30953103
finally:
3096-
self.signal_read = self.signal_write = None
3104+
self.raise_on_sigint = False
30973105

30983106
def cmdloop(self):
30993107
with (
3100-
self._block_sigint(),
3101-
self._signal_socket_pair(),
3108+
self._sigint_handler(),
31023109
self.readline_completion(self.complete),
31033110
):
31043111
while not self.write_failed:
31053112
try:
3106-
with self._handle_sigint(signal.default_int_handler):
3107-
if not (payload_bytes := self._readline()):
3108-
break
3113+
if not (payload_bytes := self._readline()):
3114+
break
31093115
except KeyboardInterrupt:
31103116
self.send_interrupt()
31113117
continue
@@ -3161,8 +3167,7 @@ def process_payload(self, payload):
31613167
def prompt_for_reply(self, prompt):
31623168
while True:
31633169
try:
3164-
with self._handle_sigint(signal.default_int_handler):
3165-
payload = {"reply": self.read_command(prompt)}
3170+
payload = {"reply": self.read_command(prompt)}
31663171
except EOFError:
31673172
payload = {"signal": "EOF"}
31683173
except KeyboardInterrupt:
@@ -3202,8 +3207,7 @@ def complete(self, text, state):
32023207
if self.write_failed:
32033208
return None
32043209

3205-
with self._handle_sigint(signal.default_int_handler):
3206-
payload = self._readline()
3210+
payload = self._readline()
32073211
if not payload:
32083212
return None
32093213

0 commit comments

Comments
 (0)