Skip to content

Commit 8d89bf4

Browse files
committed
Only allow KeyboardInterrupt at specific times
Because our goal is to proxy interrupt signals to the remote process, it's important that we only accept those signals at moments when we're ready to handle them and send them on. In particular, we're prepared to handle them when reading user input for a prompt, and when waiting for the server to send a new prompt after our last command. We do not want to accept them at other times, like while we're in the middle of printing output to the screen, as otherwise a while True: "Hello" executed at the PDB REPL can't reliably be Ctrl-C'd: it's more likely that the signal will arrive while the client is printing than while it's waiting for the next line from the server, and if so nothing will be prepared to handle the `KeyboardInterrupt` and the client will exit.
1 parent 0e21ed7 commit 8d89bf4

1 file changed

Lines changed: 48 additions & 5 deletions

File tree

Lib/pdb.py

Lines changed: 48 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -3009,12 +3009,53 @@ def readline_completion(self, completer):
30093009
finally:
30103010
readline.set_completer(old_completer)
30113011

3012+
if not hasattr(signal, "pthread_sigmask"):
3013+
# On Windows, we must drop signals arriving while we're ignoring them.
3014+
@contextmanager
3015+
def _block_sigint(self):
3016+
old_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
3017+
try:
3018+
yield
3019+
finally:
3020+
signal.signal(signal.SIGINT, old_handler)
3021+
3022+
@contextmanager
3023+
def _handle_sigint(self, handler):
3024+
old_handler = signal.signal(signal.SIGINT, handler)
3025+
try:
3026+
yield
3027+
finally:
3028+
signal.signal(signal.SIGINT, old_handler)
3029+
else:
3030+
# On Unix, we can save them to be processed later.
3031+
@contextmanager
3032+
def _block_sigint(self):
3033+
signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGINT})
3034+
try:
3035+
yield
3036+
finally:
3037+
signal.pthread_sigmask(signal.SIG_UNBLOCK, {signal.SIGINT})
3038+
3039+
@contextmanager
3040+
def _handle_sigint(self, handler):
3041+
old_handler = signal.signal(signal.SIGINT, handler)
3042+
try:
3043+
signal.pthread_sigmask(signal.SIG_UNBLOCK, {signal.SIGINT})
3044+
yield
3045+
finally:
3046+
signal.pthread_sigmask(signal.SIG_BLOCK, {signal.SIGINT})
3047+
signal.signal(signal.SIGINT, old_handler)
3048+
30123049
def cmdloop(self):
3013-
with self.readline_completion(self.complete):
3050+
with (
3051+
self._block_sigint(),
3052+
self.readline_completion(self.complete),
3053+
):
30143054
while not self.write_failed:
30153055
try:
3016-
if not (payload_bytes := self.sockfile.readline()):
3017-
break
3056+
with self._handle_sigint(signal.default_int_handler):
3057+
if not (payload_bytes := self.sockfile.readline()):
3058+
break
30183059
except KeyboardInterrupt:
30193060
self.send_interrupt()
30203061
continue
@@ -3061,7 +3102,8 @@ def process_payload(self, payload):
30613102
def prompt_for_reply(self, prompt):
30623103
while True:
30633104
try:
3064-
payload = {"reply": self.read_command(prompt)}
3105+
with self._handle_sigint(signal.default_int_handler):
3106+
payload = {"reply": self.read_command(prompt)}
30653107
except EOFError:
30663108
payload = {"signal": "EOF"}
30673109
except KeyboardInterrupt:
@@ -3101,7 +3143,8 @@ def complete(self, text, state):
31013143
if self.write_failed:
31023144
return None
31033145

3104-
payload = self.sockfile.readline()
3146+
with self._handle_sigint(signal.default_int_handler):
3147+
payload = self.sockfile.readline()
31053148
if not payload:
31063149
return None
31073150

0 commit comments

Comments
 (0)