Skip to content

wait_variable hangs at exit #40440

Description

@reowen
mannequin
BPO 978604
Nosy @loewis, @terryjreedy, @devdanzin, @asvetlov
Files
  • WaitTest.py: demo of wait_variable bug
  • issue978604.diff
  • Note: these values reflect the state of the issue at the time it was migrated and might not reflect the current state.

    Show more details

    GitHub fields:

    assignee = 'https://github.com/loewis'
    closed_at = None
    created_at = <Date 2004-06-23.23:07:12.000>
    labels = ['type-bug', 'expert-tkinter', '3.9', '3.10', '3.11']
    title = 'wait_variable hangs at exit'
    updated_at = <Date 2021-12-26.00:32:27.209>
    user = 'https://bugs.python.org/reowen'

    bugs.python.org fields:

    activity = <Date 2021-12-26.00:32:27.209>
    actor = 'ajaksu2'
    assignee = 'loewis'
    closed = False
    closed_date = None
    closer = None
    components = ['Tkinter']
    creation = <Date 2004-06-23.23:07:12.000>
    creator = 'reowen'
    dependencies = []
    files = ['1314', '13528']
    hgrepos = []
    issue_num = 978604
    keywords = ['patch']
    message_count = 8.0
    messages = ['21265', '82060', '84896', '84921', '84944', '84959', '139198', '409177']
    nosy_count = 6.0
    nosy_names = ['loewis', 'terry.reedy', 'reowen', 'ajaksu2', 'gpolo', 'asvetlov']
    pr_nums = []
    priority = 'normal'
    resolution = None
    stage = 'test needed'
    status = 'open'
    superseder = None
    type = 'behavior'
    url = 'https://bugs.python.org/issue978604'
    versions = ['Python 3.9', 'Python 3.10', 'Python 3.11']

    Linked PRs

    Activity

    1. reowen commented on Jun 23, 2004

      reowenmannequin
      MannequinAuthor

      If code is waiting on a wait_variable at program exit then the program never fully exits. The command prompt never returns and the process doesn't seem to be doing much of anything (i.e. no heavy CPU usage). ctrl-C has no effect.

      I have found that binding to <Detroy> does work around the problem.

      I saw this using a unix/X11 build of Python 2.3.4 on MacOS X 10.3.4 and also the standard Python 2.3 framework build included with MacOS X 10.3.

      The attached file gives a good example. To see the problem, execute the script and then close the root window before the script finishes (you have 10 seconds).

    2. devdanzin commented on Feb 14, 2009

      devdanzinmannequin
      Mannequin

      Confirmed on Linux (closing after 'script 2'):

      trunk-py$ ./python WaitTest.py
      script 0
      script 1
      script 2
      invalid command name "137221876callit"
      while executing
      "137221876callit"
      ("after" script)

    3. gpolo commented on Mar 31, 2009

      gpolomannequin
      Mannequin

      You can also reproduce it with a shorter test that doesn't need any
      interaction:

      import Tkinter
      
      root = Tkinter.Tk()
      waitvar = Tkinter.BooleanVar()
      
      root.after(50, lambda: waitvar.set(True))
      root.after(10, root.destroy)
      root.wait_variable(waitvar)
      
      root.mainloop()

      Verifying.

    4. gpolo commented on Mar 31, 2009

      gpolomannequin
      Mannequin

      Patch attached, but didn't test it at all. This is a trick so it doesn't
      get committed without a test (hopefully) :)

    5. gpolo commented on Mar 31, 2009

      gpolomannequin
      Mannequin

      Using this patch I noticed two problems appeared when running WaitTest.py

      1. Closing the window results in: "_tkinter.TclError: can't invoke
        "tkwait" command: application has been destroyed" which I'm not
        considering as a bug, maybe the user can get confused about this but if
        the application were doing something like WaitTest.py does then it
        should also be aware that this could happen.

      2. Pressing Ctrl-c hits a different problem, causing ::tkerror to be
        called, which I'm considering a bug that I still have to check.

    6. gpolo commented on Apr 1, 2009

      gpolomannequin
      Mannequin

      Ah.. number 2 is problematic. Before continuing lets reduce the previous
      WaitTest.py to this (which shows the same problems):

      import Tkinter
      
      root = Tkinter.Tk()
      waitVar = Tkinter.BooleanVar()
      
      root.after(3000, lambda: waitVar.set(True))
      root.wait_variable(waitVar)

      When you run this, it will schedule the call to this lambda to happen 3
      seconds later, and will block on the wait_variable call. Now suppose you
      hit Ctrl-c while you are stuck into a Tcl_EvalObjv call (Tcl_EvalObjv
      will end up being called after invoking wait_variable). Python will not
      notice this Ctrl-c for now (we are on Tcl land now), but eventually the
      event you scheduled earlier is fired. PythonCmd is called to handle it,
      which will then call the anonymous func, but since you pressed Ctrl-c it
      will return NULL and PythonCmd will call PythonCmd_Error to handle it.
      PythonCmd_Error sets errInCmd to 1, saves the exception and returns an
      TCL_ERROR indicator, this causes tkerror to be called. tkerror is a
      function created in Tkinter.py, which does nothing so that previous
      saved exception is not used. Next what happens is that the lambda
      function didn't execute, the tk variable didn't change and you are still
      blocked on wait_variable (this situation seems normal to me).

      So what I did here, and I'm not sure if it would be accepted or not, was
      create a new function in _tkinter.c called Tkinter_PrintError
      (accessible through _tkinter._print_error) that restores the saved
      exception and prints it. Now you might ask why not raise it instead ?
      Martin that is on the nosy list will agree with me, I think. Raising an
      error at this point will cause an Tk dialog to popup and it won't
      contain a proper traceback (if done this way I did), which is annoying.

      Note that even though PythonCmd_Error set errInCmd to 1, it was never
      used because the mainloop wasn't running (it also wouldn't make much
      difference to put a root.mainloop() after wait_variable since we would
      be blocked before the mainloop started).

    7. terryjreedy commented on Jun 26, 2011

      @terryjreedy
      Member

      In 3.2, winxp, (with Tkinter => tkinter), I get
      invalid command name "12277720callit"
      while executing
      "12277720callit"
      ("after" script)
      and no new prompt and ^C ineffective. Had to kill command window.
      With IDLE, get nothing and have to kill with Task Manager.
      So confirmed for 3.x.

    8. devdanzin commented on Dec 26, 2021

      devdanzinmannequin
      Mannequin

      Confirmed on Python 3.11.0a3+, assuming older versions also affected. This issue has different versions of tests to confirm it, but I think not in a test suite-friendly format yet.

      There's also a patch and detailed analysis by gpolo indicating that it might need editing _tkinter.c to handle fallout from his proposed Python-side patch.

    9. transferred this issue fromon Apr 9, 2022
    10. kurtqq commented on Apr 3, 2023

      @kurtqq

      is this still an issue?

    11. terryjreedy commented on Apr 3, 2023

      @terryjreedy
      Member

      Try a test if you want. 'devdanzin' already confirmed for 3.11 and I don't know of a fix. 3.12 uses tk 8.6.13 on Windows and that might have an effect.

    12. added
      stdlibStandard Library Python modules in the Lib/ directory
      on Nov 23, 2023
    13. serhiy-storchaka commented on Jun 29, 2026

      @serhiy-storchaka
      Member

      The hang is inherent to tkwait variable: it blocks until the variable is written, with no escape if that never happens (e.g. the application is torn down before the setter runs).

      PR #152619 adds an optional keyword-only timeout to wait_variable(). With it, the call returns False once the timeout elapses instead of blocking forever; without it the behaviour is unchanged.

    14. ThingamabobsCoding commented on Sep 3, 2026

      @ThingamabobsCoding

      The hang is inherent to tkwait variable: it blocks until the variable is written, with no escape if that never happens (e.g. the application is torn down before the setter runs).

      PR #152619 adds an optional keyword-only timeout to wait_variable(). With it, the call returns False once the timeout elapses instead of blocking forever; without it the behaviour is unchanged.

      It doesn't just "block" - It spawns a nested event-loop/main-loop.
      Tk().mainloop() is not part of the public API in tcl and the wait commands are always used instead.
      Further, the mainloop(int) takes a parameter and I think the wait_anything commands should increase this internal parameter to keep track of the nested mainloops instead of an optional timeout parameter that may cause complicated code to evaluate if the app isn't destroyed.

      This would allow an implementation of Tk().destroy() to exit all nested event-loops, since it seems fair to assume that if this function is called one expect the application to end, not just break a loop.

    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

      3.11only security fixesstdlibStandard 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