Skip to content

tkinter wait_variable can cause the event loop to spin #139145

@mdehoon

Description

@mdehoon

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

Metadata

Metadata

Assignees

No one assigned

    Labels

    stdlibStandard Library Python modules in the Lib/ directorytopic-tkintertype-bugAn unexpected behavior, bug, or error

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions