https://github.com/python/cpython/commit/46d1809ccd4bc0e1439a4696b428d2a1c2edcfbf
commit: 46d1809ccd4bc0e1439a4696b428d2a1c2edcfbf
branch: main
author: Serhiy Storchaka <[email protected]>
committer: serhiy-storchaka <[email protected]>
date: 2026-06-28T00:26:11+03:00
summary:
gh-83274: Don't crash when a Tcl interpreter is deallocated in the wrong thread
(GH-152323)
Deallocating the interpreter from a thread other than the one it was created
in ran Tcl_DeleteInterp() there, which makes Tcl abort the process
("Tcl_AsyncDelete: async handler deleted by the wrong thread").
Tkapp_Dealloc() now leaks the interpreter in that case and reports a
RuntimeWarning instead.
Co-Authored-By: E. Paine <[email protected]>
Co-Authored-By: Claude Opus 4.8 <[email protected]>
files:
A Misc/NEWS.d/next/Library/2026-06-26-16-30-00.gh-issue-83274.Kx9mQv.rst
M Lib/test/test_tkinter/test_misc.py
M Modules/_tkinter.c
diff --git a/Lib/test/test_tkinter/test_misc.py
b/Lib/test/test_tkinter/test_misc.py
index 4c003e697d23b86..a93f5dee349e648 100644
--- a/Lib/test/test_tkinter/test_misc.py
+++ b/Lib/test/test_tkinter/test_misc.py
@@ -2,6 +2,7 @@
import functools
import platform
import sys
+import textwrap
import unittest
import weakref
import tkinter
@@ -9,6 +10,7 @@
import enum
from test import support
from test.support import os_helper
+from test.support.script_helper import assert_python_ok
from test.test_tkinter.support import setUpModule # noqa: F401
from test.test_tkinter.support import (AbstractTkTest, AbstractDefaultRootTest,
requires_tk, get_tk_patchlevel,
@@ -53,6 +55,33 @@ class Button2(tkinter.Button):
b4 = Button2(f2)
self.assertEqual(len({str(b), str(b2), str(b3), str(b4)}), 4)
+ def test_dealloc_in_wrong_thread(self):
+ # gh-83274: deallocating the interpreter in the wrong thread must not
+ # crash.
+ script = textwrap.dedent("""
+ import threading
+ import tkinter
+ root = tkinter.Tk()
+ root.destroy()
+ # Let another thread drop the last reference.
+ ready = threading.Event()
+ t = threading.Thread(target=lambda obj: ready.wait(), args=(root,))
+ t.start()
+ del root
+ ready.set()
+ t.join()
+ print('ok')
+ """)
+ rc, out, err = assert_python_ok('-c', script)
+ self.assertEqual(out.strip(), b'ok')
+ if not support.Py_GIL_DISABLED:
+ # On the free-threaded build the interpreter may instead be
+ # deallocated in its own thread (deferred reference counting), so
+ # the warning is not necessarily emitted. The crucial guarantee --
+ # no crash -- is already checked by assert_python_ok() above.
+ self.assertIn(b'RuntimeWarning', err)
+ self.assertIn(b'gh-83274', err)
+
@requires_tk(8, 6, 6)
def test_tk_busy(self):
root = self.root
diff --git
a/Misc/NEWS.d/next/Library/2026-06-26-16-30-00.gh-issue-83274.Kx9mQv.rst
b/Misc/NEWS.d/next/Library/2026-06-26-16-30-00.gh-issue-83274.Kx9mQv.rst
new file mode 100644
index 000000000000000..3b722d2176be920
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2026-06-26-16-30-00.gh-issue-83274.Kx9mQv.rst
@@ -0,0 +1,3 @@
+Deallocating a :mod:`tkinter` application from a thread other than the one it
+was created in no longer crashes the interpreter. The underlying Tcl
+interpreter is leaked instead, and a :exc:`RuntimeWarning` is reported.
diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c
index 8fa58d07096e30b..1f1fe4a91addf4c 100644
--- a/Modules/_tkinter.c
+++ b/Modules/_tkinter.c
@@ -3132,10 +3132,24 @@ Tkapp_Dealloc(PyObject *op)
{
TkappObject *self = TkappObject_CAST(op);
PyTypeObject *tp = Py_TYPE(self);
- /*CHECK_TCL_APPARTMENT;*/
- ENTER_TCL
- Tcl_DeleteInterp(Tkapp_Interp(self));
- LEAVE_TCL
+ if (self->threaded && self->thread_id != Tcl_GetCurrentThread()) {
+ /* Deleting the interpreter from another thread aborts the process
+ ("Tcl_AsyncDelete: async handler deleted by the wrong thread").
+ Leak it instead (gh-83274). */
+ if (PyErr_WarnEx(PyExc_RuntimeWarning,
+ "the Tcl interpreter is leaked because it was "
+ "deallocated in a thread other than the one it was "
+ "created in (see gh-83274)", 1) < 0)
+ {
+ PyErr_FormatUnraisable("Exception ignored while finalizing "
+ "a Tcl interpreter");
+ }
+ }
+ else {
+ ENTER_TCL
+ Tcl_DeleteInterp(Tkapp_Interp(self));
+ LEAVE_TCL
+ }
Py_XDECREF(self->trace);
PyObject_Free(self);
Py_DECREF(tp);
_______________________________________________
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]