From a089060d59705ef06facdb9662bf417267663354 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Wed, 23 Sep 2026 21:29:36 +0300 Subject: [PATCH 1/2] gh-74112: Make Ctrl-C in the IDLE Shell interrupt blocking calls (GH-157662) Send a real SIGINT to the main thread of the user process instead of calling _thread.interrupt_main(), which only sets a flag checked between bytecodes. The signal is sent while holding a new lock which protects sending a message, so that the main thread is not interrupted in the middle of a message. An interrupted wait for a response now releases its lock, so that the socket thread does not deadlock. (cherry picked from commit 9232c21a1c1eb6a12b96acebbef6c86a698228e0) Co-authored-by: Serhiy Storchaka --- Lib/idlelib/idle_test/test_rpc.py | 14 +++++++ Lib/idlelib/idle_test/test_run.py | 32 +++++++++++++++ Lib/idlelib/rpc.py | 39 +++++++++++-------- Lib/idlelib/run.py | 15 ++++++- ...6-09-17-12-00-00.gh-issue-74112.sigint.rst | 2 + 5 files changed, 85 insertions(+), 17 deletions(-) create mode 100644 Misc/NEWS.d/next/IDLE/2026-09-17-12-00-00.gh-issue-74112.sigint.rst diff --git a/Lib/idlelib/idle_test/test_rpc.py b/Lib/idlelib/idle_test/test_rpc.py index 15c3ed14b8f6f3e..d929165e2e5ec90 100644 --- a/Lib/idlelib/idle_test/test_rpc.py +++ b/Lib/idlelib/idle_test/test_rpc.py @@ -3,7 +3,9 @@ from idlelib import rpc import socket import struct +import threading import unittest +from unittest import mock class SocketIOTest(unittest.TestCase): @@ -22,6 +24,18 @@ def test_reconnect_discards_partial_packet(self): new_peer.sendall(struct.pack(' 0: - try: - r, w, x = select.select([], [self.sock], []) - n = self.sock.send(s[:BUFSIZE]) - except (AttributeError, TypeError): - raise OSError("socket no longer exists") - s = s[n:] + with self.sendlock: + while len(s) > 0: + try: + r, w, x = select.select([], [self.sock], []) + n = self.sock.send(s[:BUFSIZE]) + except (AttributeError, TypeError): + raise OSError("socket no longer exists") + s = s[n:] def pollpacket(self, wait): self._stage0() diff --git a/Lib/idlelib/run.py b/Lib/idlelib/run.py index f5564ccf90f7f46..983e2a1ace043c5 100644 --- a/Lib/idlelib/run.py +++ b/Lib/idlelib/run.py @@ -9,6 +9,7 @@ import io import linecache import queue +import signal import sys import textwrap import time @@ -670,7 +671,19 @@ def runcode(self, code): def interrupt_the_server(self): if interruptible: - thread.interrupt_main() + handler = signal.getsignal(signal.SIGINT) + if handler not in (signal.SIG_DFL, signal.SIG_IGN, None): + # A real signal interrupts blocking calls such as + # time.sleep() (gh-74112). The lock prevents interrupting + # the main thread in the middle of sending a message. + with self.rpchandler.sendlock: + if hasattr(signal, 'pthread_kill'): + signal.pthread_kill(threading.main_thread().ident, + signal.SIGINT) + else: + signal.raise_signal(signal.SIGINT) + else: + thread.interrupt_main() def start_the_debugger(self, gui_adap_oid): return debugger_r.start_debugger(self.rpchandler, gui_adap_oid) diff --git a/Misc/NEWS.d/next/IDLE/2026-09-17-12-00-00.gh-issue-74112.sigint.rst b/Misc/NEWS.d/next/IDLE/2026-09-17-12-00-00.gh-issue-74112.sigint.rst new file mode 100644 index 000000000000000..18a230e75913b59 --- /dev/null +++ b/Misc/NEWS.d/next/IDLE/2026-09-17-12-00-00.gh-issue-74112.sigint.rst @@ -0,0 +1,2 @@ +Ctrl-C in the IDLE Shell now interrupts blocking calls such as +:func:`time.sleep` and :meth:`socket.recv `. From c73527f7eac89d5189dc805750cbc4bc5d28a885 Mon Sep 17 00:00:00 2001 From: Christian Aurich Zanettini Martins Date: Tue, 6 Oct 2026 12:12:15 -0300 Subject: [PATCH 2/2] gh-74112: Fix race in test_interrupt_blocking_call (GH-158914) On a loaded machine the KeyboardInterrupt could be raised before the main thread reached time.sleep(), escape the test and stop the whole regrtest run. (cherry picked from commit ea0ee925ad755999d6095ba61de2652ec9275f04) --- Lib/idlelib/idle_test/test_run.py | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/Lib/idlelib/idle_test/test_run.py b/Lib/idlelib/idle_test/test_run.py index 19adb325ec06f13..66cceb5a5ab4052 100644 --- a/Lib/idlelib/idle_test/test_run.py +++ b/Lib/idlelib/idle_test/test_run.py @@ -540,9 +540,11 @@ def test_interrupt_blocking_call(self): # gh-74112: interrupt the main thread blocked in time.sleep(). timer = threading.Timer(0.1, self.ex.interrupt_the_server) self.addCleanup(timer.join) - timer.start() start = time.monotonic() with self.assertRaises(KeyboardInterrupt): + # On a loaded machine the signal can arrive before the main + # thread reaches time.sleep(), so start the timer in the block. + timer.start() time.sleep(support.SHORT_TIMEOUT) self.assertLess(time.monotonic() - start, support.SHORT_TIMEOUT / 2)