-
-
Notifications
You must be signed in to change notification settings - Fork 32.9k
Description
Bug report
Bug description:
The following script causes the event loop to spin when Python is in interactive mode.
To reproduce this bug, run this script as python -i script.py
. Then press the top button to call the root.wait_variable
command. Once waiting, press "enter" in the command window. Monitoring top
will show you that CPU usage goes up to 100%.
The reason is that EventHook
(in _tkinter.c
) calls Tcl_CreateFileHandler
to ask Tcl to monitor stdin.
root.wait_variable
calls Tk_TkwaitObjCmd
, which will sit in the following tight event loop (in generic/tkCmds.c
in the Tk source):
done = 0;
while (!done) {
if (Tcl_Canceled(interp, TCL_LEAVE_ERR_MSG) == TCL_ERROR) {
code = TCL_ERROR;
break;
}
Tcl_DoOneEvent(0);
}
But because this event loop starts before EventHook
exits, Tcl is still monitoring stdin.
Therefore, Tcl_DoOneEvent
detects activity on stdin, resulting in a call to MyFileProc
(in _tkinter.c
), which duly sets stdin_ready
to 1 without reading the input on stdin. But the tight event loop above doesn't know about stdin_ready
, and therefore keeps running, calling Tcl_DoOneEvent
again, which finds the same input still available on stdin. Thus this tight event loop keeps spinning, causing the CPU usage to go up to 100%.
Note that calling trigger_wait_variable
from the Python command prompt (instead of pressing the button) will not result in a spinning event loop, as in that case EventHook
exits first, calling Tcl_DeleteFileHandler
on stdin before trigger_wait_variable
is executed. Therefore in that case Tcl is no longer monitoring stdin when entering the tight event loop above.
This was tested on Linux.
import tkinter
root = tkinter.Tk()
root.title("wait_variable Demo")
py_var = tkinter.StringVar()
label = tkinter.Label(root, text="click the top button to start waiting", padx=20, pady=10)
label.pack()
def trigger_wait_variable():
label.config(text="click the bottom button to stop waiting")
print("click the bottom button to stop waiting")
root.wait_variable(py_var) # blocks until py_var is set
print("finished waiting")
label.config(text=f"finished waiting")
tkinter.Button(root, text="call wait_variable to start waiting", command=trigger_wait_variable).pack()
def set_py_var():
py_var.set("done from wait_variable button")
tkinter.Button(root, text="stop waiting", command=set_py_var).pack()
CPython versions tested on:
3.12
Operating systems tested on:
Linux