Skip to content

Commit c2fedea

Browse files
miss-islingtonserhiy-storchakamdehoonchrstphrchvzclaude
authored
[3.14] gh-139145: Fix tkinter event loop in interactive mode (GH-152257) (GH-152292)
When a Tcl command running its own event loop (such as vwait or wait_variable) was active and the user typed input on stdin, the event loop kept spinning at 100% CPU. The stdin file handler is now removed as soon as input becomes available. Also fix gh-139816: an exception raised in a callback no longer stops the event loop to wait for Enter on a Python built without readline; pending callbacks keep running until input is actually available on stdin. (cherry picked from commit 3ffda34) Co-authored-by: Serhiy Storchaka <storchaka@gmail.com> Co-authored-by: mdehoon <mjldehoon@yahoo.com> Co-authored-by: Christopher Chavez <chrischavez@gmx.us> Co-authored-by: Claude Opus 4.8 <noreply@anthropic.com>
1 parent e1dc5f5 commit c2fedea

3 files changed

Lines changed: 30 additions & 9 deletions

File tree

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
Fix a busy loop in :mod:`tkinter` on interactive Python. When a Tcl command
2+
running its own event loop (such as ``vwait`` or :meth:`!wait_variable`) was
3+
active and input arrived on stdin, the event loop kept spinning at 100% CPU.
4+
The stdin file handler is now removed as soon as input is available. Based on
5+
a patch by Michiel de Hoon.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
Fix a hang in :mod:`tkinter` on interactive Python built without
2+
:mod:`readline`. An exception raised in a callback no longer causes the
3+
event loop to stop and wait for the user to press Enter; pending callbacks
4+
now keep running until input is actually available on stdin.

Modules/_tkinter.c

Lines changed: 21 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -3392,7 +3392,13 @@ static int stdin_ready = 0;
33923392
static void
33933393
MyFileProc(void *clientData, int mask)
33943394
{
3395+
int tfile = (int)(Py_intptr_t)clientData;
33953396
stdin_ready = 1;
3397+
/* Stop watching stdin now that input is available. Doing it here rather
3398+
than after the loop below ensures that a nested event loop (e.g. the one
3399+
started by wait_variable) does not keep waking up on the same unread
3400+
input, spinning at 100% CPU. */
3401+
Tcl_DeleteFileHandler(tfile);
33963402
}
33973403
#endif
33983404

@@ -3409,9 +3415,10 @@ EventHook(void)
34093415
errorInCmd = 0;
34103416
#ifndef MS_WINDOWS
34113417
tfile = fileno(stdin);
3412-
Tcl_CreateFileHandler(tfile, TCL_READABLE, MyFileProc, NULL);
3418+
Tcl_CreateFileHandler(tfile, TCL_READABLE, MyFileProc,
3419+
(void *)(Py_intptr_t)tfile);
34133420
#endif
3414-
while (!errorInCmd && !stdin_ready) {
3421+
while (!stdin_ready) {
34153422
int result;
34163423
#ifdef MS_WINDOWS
34173424
if (_kbhit()) {
@@ -3431,18 +3438,23 @@ EventHook(void)
34313438
Sleep(Tkinter_busywaitinterval);
34323439
Py_END_ALLOW_THREADS
34333440

3441+
/* Report an exception raised in a callback, but keep pumping events
3442+
instead of returning to the prompt: without readline there is no
3443+
input waiting on stdin yet, so returning here would block in fgets
3444+
until the user hits enter, freezing later callbacks. */
3445+
if (errorInCmd) {
3446+
errorInCmd = 0;
3447+
PyErr_SetRaisedException(excInCmd);
3448+
excInCmd = NULL;
3449+
PyErr_Print();
3450+
}
34343451
if (result < 0)
34353452
break;
34363453
}
34373454
#ifndef MS_WINDOWS
3438-
Tcl_DeleteFileHandler(tfile);
3455+
if (!stdin_ready)
3456+
Tcl_DeleteFileHandler(tfile);
34393457
#endif
3440-
if (errorInCmd) {
3441-
errorInCmd = 0;
3442-
PyErr_SetRaisedException(excInCmd);
3443-
excInCmd = NULL;
3444-
PyErr_Print();
3445-
}
34463458
PyEval_SaveThread();
34473459
return 0;
34483460
}

0 commit comments

Comments
 (0)