Skip to content

Commit 52bdfcc

Browse files
miss-islingtonserhiy-storchakamdehoonchrstphrchvzclaude
authored
[3.15] gh-139145: Fix tkinter event loop in interactive mode (GH-152257) (GH-152291)
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 f2402cb commit 52bdfcc

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
@@ -3433,7 +3433,13 @@ static int stdin_ready = 0;
34333433
static void
34343434
MyFileProc(void *clientData, int mask)
34353435
{
3436+
int tfile = (int)(Py_intptr_t)clientData;
34363437
stdin_ready = 1;
3438+
/* Stop watching stdin now that input is available. Doing it here rather
3439+
than after the loop below ensures that a nested event loop (e.g. the one
3440+
started by wait_variable) does not keep waking up on the same unread
3441+
input, spinning at 100% CPU. */
3442+
Tcl_DeleteFileHandler(tfile);
34373443
}
34383444
#endif
34393445

@@ -3450,9 +3456,10 @@ EventHook(void)
34503456
errorInCmd = 0;
34513457
#ifndef MS_WINDOWS
34523458
tfile = fileno(stdin);
3453-
Tcl_CreateFileHandler(tfile, TCL_READABLE, MyFileProc, NULL);
3459+
Tcl_CreateFileHandler(tfile, TCL_READABLE, MyFileProc,
3460+
(void *)(Py_intptr_t)tfile);
34543461
#endif
3455-
while (!errorInCmd && !stdin_ready) {
3462+
while (!stdin_ready) {
34563463
int result;
34573464
#ifdef MS_WINDOWS
34583465
if (_kbhit()) {
@@ -3472,18 +3479,23 @@ EventHook(void)
34723479
Sleep(Tkinter_busywaitinterval);
34733480
Py_END_ALLOW_THREADS
34743481

3482+
/* Report an exception raised in a callback, but keep pumping events
3483+
instead of returning to the prompt: without readline there is no
3484+
input waiting on stdin yet, so returning here would block in fgets
3485+
until the user hits enter, freezing later callbacks. */
3486+
if (errorInCmd) {
3487+
errorInCmd = 0;
3488+
PyErr_SetRaisedException(excInCmd);
3489+
excInCmd = NULL;
3490+
PyErr_Print();
3491+
}
34753492
if (result < 0)
34763493
break;
34773494
}
34783495
#ifndef MS_WINDOWS
3479-
Tcl_DeleteFileHandler(tfile);
3496+
if (!stdin_ready)
3497+
Tcl_DeleteFileHandler(tfile);
34803498
#endif
3481-
if (errorInCmd) {
3482-
errorInCmd = 0;
3483-
PyErr_SetRaisedException(excInCmd);
3484-
excInCmd = NULL;
3485-
PyErr_Print();
3486-
}
34873499
PyEval_SaveThread();
34883500
return 0;
34893501
}

0 commit comments

Comments
 (0)