Skip to content

gh-74112: Make Ctrl-C in the IDLE Shell interrupt blocking calls - #157662

Merged
serhiy-storchaka merged 2 commits into
python:mainfrom
serhiy-storchaka:gh-74112-interrupt-signal
Sep 23, 2026
Merged

serhiy-storchaka merged 2 commits into
python:mainfrom
serhiy-storchaka:gh-74112-interrupt-signal

Conversation

@serhiy-storchaka

Copy link
Copy Markdown
Member

_thread.interrupt_main() only sets a flag which is checked between bytecodes, so Ctrl-C did not interrupt time.sleep(), socket.recv() and other blocking calls. Now a real SIGINT is sent to the main thread of the user process: with signal.pthread_kill(), or with signal.raise_signal() on Windows, where the C signal handler wakes up the main thread. _thread.interrupt_main() is still used when SIGINT is not handled by Python.

Sending a message is now protected by a lock, and the signal is sent while holding it. Otherwise the main thread could be interrupted in the middle of a message, and the GUI process would wait for its end forever; this happened regularly on Windows during large output. An interrupted wait for a response now releases its lock, so that the socket thread does not deadlock (the problem observed in #2466).

Tested on Linux and Windows with time.sleep(), input(), loops with large output, and socket.recv() (not interruptible on Windows, as in a console).

🤖 Generated with Claude Code

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.
@serhiy-storchaka serhiy-storchaka added needs backport to 3.13 bugs and security fixes needs backport to 3.14 bugs and security fixes needs backport to 3.15 pre-release feature fixes, bugs and security fixes labels Sep 17, 2026
@serhiy-storchaka

Copy link
Copy Markdown
Member Author

@terryjreedy, to test manually: start IDLE, then in the Shell enter each of the following, wait a second, and press Ctrl-C. Each should print a KeyboardInterrupt traceback immediately and show a new prompt, and the Shell should keep working afterwards.

  1. import time; time.sleep(60) — before this PR, nothing happens for 60 s.
  2. input() — should not freeze IDLE.
  3. import sys and then while True: sys.stdout.write('x'*200000) (press Enter twice) — before this PR, on Windows this could freeze IDLE waiting for the end of a partially sent message.
  4. import socket; s = socket.socket(socket.AF_INET, socket.SOCK_DGRAM); s.bind(('127.0.0.1', 0)); s.recv(100) — interrupted on Linux and macOS; not on Windows, as in a console.
  5. import signal; signal.signal(signal.SIGINT, signal.SIG_IGN) and then time.sleep(10) — Ctrl-C is ignored, as before this PR and as in a console.

@serhiy-storchaka

Copy link
Copy Markdown
Member Author

The macOS failure is caused by Tk replacing Python's SIGINT handler with its own, which exits the process (#157672). #157673 fixes this in _tkinter; this PR should be merged after it.

@terryjreedy

Copy link
Copy Markdown
Member

1, 2, and 3 interrupted as desired. VERY nice! For 4 and 5, restart worked fine. They are not things beginners are likely to do unless exploring the unknown.

My main concern reviewing and merging is avoiding regressions. I used the debugger to exercise rpc in other ways and it seemed normal. I presume wrapping chunks of code in 'with lock:' should generally be safer, with a small time cost not noticeable here.

@terryjreedy

Copy link
Copy Markdown
Member

You merged the _tkinter fix; merge this now?

@terryjreedy

Copy link
Copy Markdown
Member

Note: ^C already worked (as on 3.15) for 2 and 3. time.sleep (1) requires this patch. I suspect same of some other cases.

@terryjreedy

terryjreedy commented Sep 23, 2026 •

Copy link
Copy Markdown
Member

Perhaps just rerun macOS tests? EDIT I see you already reran tests

@serhiy-storchaka
serhiy-storchaka merged commit 9232c21 into python:main Sep 23, 2026
52 checks passed
@miss-islington-app

Copy link
Copy Markdown

Thanks @serhiy-storchaka for the PR 🌮🎉.. I'm working now to backport this PR to: 3.13, 3.14, 3.15.
🐍🍒⛏🤖

@serhiy-storchaka
serhiy-storchaka deleted the gh-74112-interrupt-signal branch September 23, 2026 18:29
@miss-islington-app

Copy link
Copy Markdown

Sorry, @serhiy-storchaka, I could not cleanly backport this to 3.15 due to a conflict.
Please backport using cherry_picker on command line.

cherry_picker 9232c21a1c1eb6a12b96acebbef6c86a698228e0 3.15

@bedevere-app

bedevere-app Bot commented Sep 23, 2026

Copy link
Copy Markdown

GH-158016 is a backport of this pull request to the 3.14 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.14 bugs and security fixes label Sep 23, 2026
@bedevere-app

bedevere-app Bot commented Sep 23, 2026

Copy link
Copy Markdown

GH-158017 is a backport of this pull request to the 3.13 branch.

@bedevere-app bedevere-app Bot removed the needs backport to 3.13 bugs and security fixes label Sep 23, 2026
@serhiy-storchaka

Copy link
Copy Markdown
Member Author

3.15 needs the _tkinter fix and maybe some other pending backports.

serhiy-storchaka added a commit that referenced this pull request Sep 23, 2026
…ls (GH-157662) (GH-158017)

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 9232c21)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
serhiy-storchaka added a commit that referenced this pull request Sep 23, 2026
…ls (GH-157662) (GH-158016)

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 9232c21)

Co-authored-by: Serhiy Storchaka <storchaka@gmail.com>
@terryjreedy

Copy link
Copy Markdown
Member

I made a note on my backport list. The IDLE project is not taking new entries today, so made a continuation list on my machine.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

needs backport to 3.15 pre-release feature fixes, bugs and security fixes

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants