Skip to content

gh-161353: Fix handle leak in test_tcl - #161368

Merged
vstinner merged 2 commits into
python:mainfrom
vstinner:test_tcl_finalize_thread
Nov 6, 2026
Merged

vstinner merged 2 commits into
python:mainfrom
vstinner:test_tcl_finalize_thread

Conversation

@vstinner

@vstinner vstinner commented Nov 4, 2026 •

Copy link
Copy Markdown
Member

Fix handle leaks in test_tcl when the test is run with -R 3:3. Call explicitly Tcl_FinalizeThread() in test_returning_deeply_nested() to clean up explicitly per-thread state. The test is run in a thread because of the @run_with_limited_c_stack() decorator.

On Windows, Tcl_CreateInterp() creates an event in the Tcl notifier implementation. The event is stored in per-thread state which is only cleaned up at exit.

Add _tkinter.finalize_thread() function.

Fix handle leaks in test_tcl when the test is run with -R 3:3. Call
explicitly Tcl_FinalizeThread() in test_returning_deeply_nested() to
clean up explicitly per-thread state. The test is run in a thread
because of the @run_with_limited_c_stack() decorator.

On Windows, Tcl_CreateInterp() creates an event in the Tcl notifier
implementation. The event is stored in per-thread state which is only
cleaned up at exit.

Add _tkinter.finalize_thread() function.
@vstinner

vstinner commented Nov 4, 2026

Copy link
Copy Markdown
Member Author

See #161353 (comment) for the longer rationale.

@serhiy-storchaka @terryjreedy @gpshead: Would you mind to review my fix?

@vstinner

vstinner commented Nov 4, 2026

Copy link
Copy Markdown
Member Author

This fix should be backported to 3.15 once #161071 will be merged.

@hugovk hugovk added needs backport to 3.15 bugs and security fixes and removed needs backport to 3.15 bugs and security fixes labels Nov 5, 2026
@vstinner

vstinner commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

An alternative would be to register an atexit callback with threading._register_atexit() to call _tkinter.finalize_thread(). But I don't understand well if Tcl/Tk already cleans the thread state automatically when a thread exits, or only when the process exits.

I mean, I don't understand well if the handle leak (one Windows event) only impacts tests, or call impact real Python code.

@encukou

encukou commented Nov 5, 2026

Copy link
Copy Markdown
Member

!buildbot AMD64.Windows.Server.2025.Refleaks.

@bedevere-bot

Copy link
Copy Markdown

🤖 New build scheduled with the buildbot fleet by @encukou for commit bc80f6a 🤖

Results will be shown at:

https://buildbot.python.org/all/#/grid?branch=refs%2Fpull%2F161368%2Fmerge

The command will test the builders whose names match following regular expression: AMD64.Windows.Server.2025.Refleaks.

The builders matched are:

  • AMD64 Windows Server 2025 Refleaks PR

@serhiy-storchaka
serhiy-storchaka self-requested a review November 5, 2026 18:53
@serhiy-storchaka

Copy link
Copy Markdown
Member

_tkinter.finalize_thread() is a large footgun. It does far more than close that event handle, and the damage is silent until later. I will try to find more focused fix, but I afraid that the only feasible solution is to change the test.

@serhiy-storchaka

Copy link
Copy Markdown
Member

Well, the simplest fix is to skip the test in the leak-hunting mode.

The leak is still here, but it is small, and once per thread.

@vstinner

vstinner commented Nov 5, 2026

Copy link
Copy Markdown
Member Author

_tkinter.finalize_thread() is a large footgun. It does far more than close that event handle, and the damage is silent until later.

In my PR, _tkinter.finalize_thread() is called when the thread exits, so I'm not sure of what you mean by "later"?

Do you mean executing more Tcl/Tk code after calling _tkinter.finalize_thread()?

Would you prefer to rename the function to _tkinter._finalize_thread() (add an underscore prefix) to highlight that it should only be used in tests, or if you really understand what you're doing?

@serhiy-storchaka

Copy link
Copy Markdown
Member

Adding an underscore and changing the docstring will help. But skipping the test is simpler.

@vstinner

vstinner commented Nov 6, 2026

Copy link
Copy Markdown
Member Author

An alternative would be to register an atexit callback with threading._register_atexit() to call _tkinter.finalize_thread().

Oh wait, I misunderstood threading._register_atexit(): it's not per-thread! In fact, I'm looking for #58281 which was no implemented yet.

Adding an underscore and changing the docstring will help. But skipping the test is simpler.

I added an underscore. How do you want to change the docstring? The function is documented at https://www.tcl-lang.org/man/tcl8.2.3/TclLib/Exit.htm as:

Tcl_ExitThread is used to terminate the current thread and invoke per-thread exit handlers. This finalization is done by Tcl_FinalizeThread, which you can call if you just want to clean up per-thread state and invoke the thread exit handlers. Tcl_Finalize calls Tcl_FinalizeThread for the current thread automatically.

I wasn't sure about the "invoke the thread exit handlers" part so I omitted it.

@vstinner
vstinner enabled auto-merge (squash) November 6, 2026 16:46
@vstinner

vstinner commented Nov 6, 2026

Copy link
Copy Markdown
Member Author

Adding an underscore and changing the docstring will help. But skipping the test is simpler.

I prefer to not skip the test. It's useful to run it on Refleak buildbots. For example, it catched this leak :-)

The buildbot is broken for 6 days. That's way longer than what's expected on a Tier-1 platform: https://peps.python.org/pep-0011/#tier-1. So I enabled auto-merge to fix the buildbot regression.

IMO this change only hides a real bug. When a Python thread calls Tcl_CreateInterp(), we should call Tcl_FinalizeThread() when the Python thread exits, especially if it's not the main thread. But I leave this as an exercice to someone else more motivated than me to fix the issue :-)

@vstinner
vstinner merged commit 2781ffe into python:main Nov 6, 2026
53 checks passed
@vstinner
vstinner deleted the test_tcl_finalize_thread branch November 6, 2026 17:19
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants