https://github.com/python/cpython/commit/e7dccf087096d39dc5de1312be4e4bacd9e021ef
commit: e7dccf087096d39dc5de1312be4e4bacd9e021ef
branch: 3.15
author: Gregory P. Smith <[email protected]>
committer: vstinner <[email protected]>
date: 2026-11-09T12:02:02+01:00
summary:

[3.15] gh-161353: Fix handle leak in test_tcl (GH-161368) (#161650)

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.
(cherry picked from commit 2781ffe1f29b0ecf64b011c927ad71df2321b6af)

Co-authored-by: Victor Stinner <[email protected]>

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 0707b05af03a433..1442cb18dd05691 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_setvar_evil(self):
         # gh-161388
diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c
index b4a0215d11edc1e..09928b6e1a849ba 100644
--- a/Modules/_tkinter.c
+++ b/Modules/_tkinter.c
@@ -3399,6 +3399,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[] =
@@ -3490,6 +3504,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 3147cf7f9d2cff3..27bad6cd33e6665 100644
--- a/Modules/clinic/_tkinter.c.h
+++ b/Modules/clinic/_tkinter.c.h
@@ -960,6 +960,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) */
@@ -967,4 +985,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=c807adb73e305725 input=a9049054013a1b77]*/
+/*[clinic end generated code: output=83f034c0d0887f2e 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