https://github.com/python/cpython/commit/2781ffe1f29b0ecf64b011c927ad71df2321b6af
commit: 2781ffe1f29b0ecf64b011c927ad71df2321b6af
branch: main
author: Victor Stinner <[email protected]>
committer: vstinner <[email protected]>
date: 2026-11-06T17:19:36Z
summary:

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

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 the private _tkinter._finalize_thread() function.

files:
M Lib/test/test_tcl.py
M Modules/_tkinter.c
M Modules/clinic/_tkinter.c.h

diff --git a/Lib/test/test_tcl.py b/Lib/test/test_tcl.py
index 1d90ac10ad77ade..31ef1457ef3944e 100644
--- a/Lib/test/test_tcl.py
+++ b/Lib/test/test_tcl.py
@@ -595,6 +595,11 @@ def test_returning_deeply_nested(self):
         interp.eval('set x a; for {set i 0} {$i < %d} {incr i} {set x [list 
$x]}'
                     % support.exceeds_recursion_limit())
         self.assertRaises(RecursionError, interp.getvar, 'x')
+        interp = None
+
+        # gh-161353: Clean up explicitly per-thread state to avoid leaking an
+        # event handle on Windows when test_tcl is run with -R 3:3 option.
+        _tkinter._finalize_thread()
 
     def test_user_command(self):
         result = None
diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c
index 9a09687e55b9817..9540839b9682bca 100644
--- a/Modules/_tkinter.c
+++ b/Modules/_tkinter.c
@@ -3584,6 +3584,20 @@ _tkinter_getbusywaitinterval_impl(PyObject *module)
     return Tkinter_busywaitinterval;
 }
 
+/*[clinic input]
+_tkinter._finalize_thread
+
+Clean up per-thread state.
+[clinic start generated code]*/
+
+static PyObject *
+_tkinter__finalize_thread_impl(PyObject *module)
+/*[clinic end generated code: output=4ed0d3be4d1f8de8 input=ea68096ee9b83d23]*/
+{
+    Tcl_FinalizeThread();
+    Py_RETURN_NONE;
+}
+
 #include "clinic/_tkinter.c.h"
 
 static PyMethodDef Tktt_methods[] =
@@ -3681,6 +3695,7 @@ static PyMethodDef moduleMethods[] =
     _TKINTER_CREATE_METHODDEF
     _TKINTER_SETBUSYWAITINTERVAL_METHODDEF
     _TKINTER_GETBUSYWAITINTERVAL_METHODDEF
+    _TKINTER__FINALIZE_THREAD_METHODDEF
     {NULL,                 NULL}
 };
 
diff --git a/Modules/clinic/_tkinter.c.h b/Modules/clinic/_tkinter.c.h
index 1e197167409a033..22529aaf7bee449 100644
--- a/Modules/clinic/_tkinter.c.h
+++ b/Modules/clinic/_tkinter.c.h
@@ -956,6 +956,24 @@ _tkinter_getbusywaitinterval(PyObject *module, PyObject 
*Py_UNUSED(ignored))
     return return_value;
 }
 
+PyDoc_STRVAR(_tkinter__finalize_thread__doc__,
+"_finalize_thread($module, /)\n"
+"--\n"
+"\n"
+"Clean up per-thread state.");
+
+#define _TKINTER__FINALIZE_THREAD_METHODDEF    \
+    {"_finalize_thread", (PyCFunction)_tkinter__finalize_thread, METH_NOARGS, 
_tkinter__finalize_thread__doc__},
+
+static PyObject *
+_tkinter__finalize_thread_impl(PyObject *module);
+
+static PyObject *
+_tkinter__finalize_thread(PyObject *module, PyObject *Py_UNUSED(ignored))
+{
+    return _tkinter__finalize_thread_impl(module);
+}
+
 #ifndef _TKINTER_TKAPP_CREATEFILEHANDLER_METHODDEF
     #define _TKINTER_TKAPP_CREATEFILEHANDLER_METHODDEF
 #endif /* !defined(_TKINTER_TKAPP_CREATEFILEHANDLER_METHODDEF) */
@@ -963,4 +981,4 @@ _tkinter_getbusywaitinterval(PyObject *module, PyObject 
*Py_UNUSED(ignored))
 #ifndef _TKINTER_TKAPP_DELETEFILEHANDLER_METHODDEF
     #define _TKINTER_TKAPP_DELETEFILEHANDLER_METHODDEF
 #endif /* !defined(_TKINTER_TKAPP_DELETEFILEHANDLER_METHODDEF) */
-/*[clinic end generated code: output=229ae8ed00ee4e40 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=c94e363a6f9de67d input=a9049054013a1b77]*/

_______________________________________________
Python-checkins mailing list -- [email protected]
To unsubscribe send an email to [email protected]
https://mail.python.org/mailman3//lists/python-checkins.python.org
Member address: [email protected]

Reply via email to