Skip to content

Commit fba87d7

Browse files
miss-islingtonserhiy-storchakamdehoonchrstphrchvzclaude
authored
[3.13] gh-139145: Fix tkinter event loop in interactive mode (GH-152257) (GH-152293)
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 ac3b2ab commit fba87d7

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
@@ -3357,7 +3357,13 @@ static int stdin_ready = 0;
33573357
static void
33583358
MyFileProc(void *clientData, int mask)
33593359
{
3360+
int tfile = (int)(Py_intptr_t)clientData;
33603361
stdin_ready = 1;
3362+
/* Stop watching stdin now that input is available. Doing it here rather
3363+
than after the loop below ensures that a nested event loop (e.g. the one
3364+
started by wait_variable) does not keep waking up on the same unread
3365+
input, spinning at 100% CPU. */
3366+
Tcl_DeleteFileHandler(tfile);
33613367
}
33623368
#endif
33633369

@@ -3374,9 +3380,10 @@ EventHook(void)
33743380
errorInCmd = 0;
33753381
#ifndef MS_WINDOWS
33763382
tfile = fileno(stdin);
3377-
Tcl_CreateFileHandler(tfile, TCL_READABLE, MyFileProc, NULL);
3383+
Tcl_CreateFileHandler(tfile, TCL_READABLE, MyFileProc,
3384+
(void *)(Py_intptr_t)tfile);
33783385
#endif
3379-
while (!errorInCmd && !stdin_ready) {
3386+
while (!stdin_ready) {
33803387
int result;
33813388
#ifdef MS_WINDOWS
33823389
if (_kbhit()) {
@@ -3396,18 +3403,23 @@ EventHook(void)
33963403
Sleep(Tkinter_busywaitinterval);
33973404
Py_END_ALLOW_THREADS
33983405

3406+
/* Report an exception raised in a callback, but keep pumping events
3407+
instead of returning to the prompt: without readline there is no
3408+
input waiting on stdin yet, so returning here would block in fgets
3409+
until the user hits enter, freezing later callbacks. */
3410+
if (errorInCmd) {
3411+
errorInCmd = 0;
3412+
PyErr_SetRaisedException(excInCmd);
3413+
excInCmd = NULL;
3414+
PyErr_Print();
3415+
}
33993416
if (result < 0)
34003417
break;
34013418
}
34023419
#ifndef MS_WINDOWS
3403-
Tcl_DeleteFileHandler(tfile);
3420+
if (!stdin_ready)
3421+
Tcl_DeleteFileHandler(tfile);
34043422
#endif
3405-
if (errorInCmd) {
3406-
errorInCmd = 0;
3407-
PyErr_SetRaisedException(excInCmd);
3408-
excInCmd = NULL;
3409-
PyErr_Print();
3410-
}
34113423
PyEval_SaveThread();
34123424
return 0;
34133425
}

0 commit comments

Comments
 (0)