Skip to content

tkinter wait_variable can cause the event loop to spin #139145

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

Linked PRs

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

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