Skip to content

test_tcl leaked [1, 1, 1] handles on AMD64 Windows Server 2025 Refleaks 3.x #161353

Open

Description

@vstinner

AMD64 Windows Server 2025 Refleaks 3.x: https://buildbot.python.org/#/builders/2263/builds/454

Error: test_tcl leaked [1, 1, 1] handles, sum=3

test.pythoninfo:

tkinter.TCL_PATCH_LEVEL: 9.0.4
tkinter.TK_PATCH_LEVEL: 9.0.4
tkinter.info_patchlevel: 9.0.4

cc @terryjreedy @serhiy-storchaka

Linked PRs

Activity

  1. added
    type-bugAn unexpected behavior, bug, or error
    testsTests in the Lib/test dir
    on Nov 4, 2026
  2. hugovk commented on Nov 4, 2026

    @hugovk
    Member

    I think this is caused by issue #160176 / PR #160177.

  3. vstinner commented on Nov 4, 2026

    @vstinner
    MemberAuthor

    I think this is caused by issue #160176 / PR #160177.

    Yes, I confirm using git bisect that the regression was introduced by commit 9c0cfb1:

    commit 9c0cfb141c236c902954841bb6cca407f86e1eeb (HEAD)
    Author: Gregory P. Smith <68491+gpshead@users.noreply.github.com>
    Date:   Sun Nov 1 00:37:26 2026 -0700
    
        gh-160176: Fix crash in tkinter on deeply nested or recursive lists (GH-160177)
    
        Converting a nested list or tuple to a Tcl object now raises ValueError
        above 1000 levels, the same limit _flatten() uses.  Converting a deeply
        nested Tcl list back to Python now raises RecursionError.
    
        ANT-2026-7UKC5QED
    
  4. vstinner commented on Nov 4, 2026

    @vstinner
    MemberAuthor

    The regression comes from the fact that the new test_returning_deeply_nested() test uses @support.run_with_limited_c_stack() decorator which runs the test in a new thread. The following test is enough to reproduce the issue:

    import unittest
    import _tkinter
    
    class Test(unittest.TestCase):
        def test_returning_deeply_nested(self):
            #interp = tkinter.Tk(None, None, 'Tk', False)
            import threading
    
            def work():
                interp = _tkinter.create(None, 'basename', 'Tk', False, 1, True, False, None)
                interp = None
    
            th = threading.Thread(target=work)
            th.start()
            th.join()
    
    if __name__ == "__main__":
        unittest.main()

    According to handle64.exe, the test creates ("leaks"?) a new "Event" handle.

  5. vstinner commented on Nov 4, 2026

    @vstinner
    MemberAuthor

    Sadly, I still reproduce the leak if I modify _tkinter.create() to do just:

        Tcl_Interp *interp = Tcl_CreateInterp();
        Tcl_DeleteInterp(interp);
        Py_RETURN_NONE;

    So the Windows event handle is not created by Python, but by Tcl.

  6. vstinner commented on Nov 4, 2026

    @vstinner
    MemberAuthor

    Analyze of Claude Code on tcl9.0.4 source code:

    The leaking Event handle is created by TclpInitNotifier() in win/tclWinNotify.c at line 126:

    tsdPtr->event = CreateEventW(NULL, TRUE /* manual /,
    FALSE /
    !signaled */, NULL);

    Here's the chain: Tcl_CreateInterp() calls Tcl_InitSubsystems() (tclEvent.c:870), which calls TclInitNotifier() (tclEvent.c:1170), which calls TclpInitNotifier() (tclNotify.c:139), which executes the CreateEventW().

    The Event is stored in thread-specific data (tsdPtr->event) and is only closed in TclpFinalizeNotifier() (tclWinNotify.c:171). But TclpFinalizeNotifier is only called from Tcl_FinalizeThread() or Tcl_Finalize() — Tcl_DeleteInterp() does not trigger notifier finalization. So if an embedder does Tcl_CreateInterp() + Tcl_DeleteInterp() without a subsequent Tcl_Finalize(), the Event handle leaks.

    I confirm that calling Tcl_FinalizeThread() solves the handle leak.

    Tcl_FinalizeThread() => TclFinalizeNotifier() => Tcl_FinalizeNotifier() => TclpFinalizeNotifier() => CloseHandle(tsdPtr->event).

  7. vstinner commented on Nov 4, 2026

    @vstinner
    MemberAuthor

    I created #161368 to fix the test_tcl handle leak on Windows.

  8. added a commit that references this issue on Nov 6, 2026
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

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions