diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index 1e089747a91ee5d..69a08c22b52eaf8 100644 --- a/Lib/tkinter/test/test_tkinter/test_misc.py +++ b/Lib/tkinter/test/test_tkinter/test_misc.py @@ -1,10 +1,22 @@ import unittest +import subprocess +import sys +import threading import tkinter from test import support from tkinter.test.support import AbstractTkTest support.requires('gui') +def test_thread_gc(): + root = tkinter.Tk() + root.destroy() + ev = threading.Event() + threading.Thread(target=lambda obj: ev.wait(), args=(root,)).start() + del root + ev.set() + print("passed") + class MiscTest(AbstractTkTest, unittest.TestCase): def test_all(self): @@ -26,6 +38,25 @@ def test_repr(self): f = tkinter.Frame(t, name='child') self.assertEqual(repr(f), '') + def test_thread_gc(self): + p = subprocess.Popen([sys.executable, "-c", + "from tkinter.test.test_tkinter import test_misc; " + "test_misc.test_thread_gc()"], + stdout=subprocess.PIPE, stderr=subprocess.PIPE) + stdout, stderr = p.communicate() + stderr = stderr.strip() + try: + self.assertTrue(b"Exception ignored in: " in stderr) + self.assertTrue(b"RuntimeWarning: Deallocation of Tkapp attempted in wrong " + b"thread. Skipping deletion of Tcl interpreter (this will " + b"cause a memory leak)." in stderr) + except AssertionError: + # If there is an error in the subprocess + # we need to be able to debug it + print(stderr.decode("utf-8"), file=sys.stderr) + raise + self.assertEqual(stdout.strip(), b"passed") + def test_generated_names(self): t = tkinter.Toplevel(self.root) f = tkinter.Frame(t) diff --git a/Misc/NEWS.d/next/Library/2020-07-18-13-44-24.bpo-39093.yhmJSn.rst b/Misc/NEWS.d/next/Library/2020-07-18-13-44-24.bpo-39093.yhmJSn.rst new file mode 100644 index 000000000000000..860dc64313911bb --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-07-18-13-44-24.bpo-39093.yhmJSn.rst @@ -0,0 +1 @@ +Fixed crash when Tcl interpreter was deallocated in wrong thread \ No newline at end of file diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 793c5e715488463..daeebd2ebb8d422 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -3040,10 +3040,19 @@ static void Tkapp_Dealloc(PyObject *self) { PyObject *tp = (PyObject *) Py_TYPE(self); - /*CHECK_TCL_APPARTMENT;*/ - ENTER_TCL - Tcl_DeleteInterp(Tkapp_Interp(self)); - LEAVE_TCL + if (((TkappObject *)self)->threaded && \ + ((TkappObject *)self)->thread_id != Tcl_GetCurrentThread()) { + // We cannot delete the interpreter in the wrong thread (bpo-39093) + PyErr_SetString(PyExc_RuntimeWarning, "Deallocation of Tkapp " \ + "attempted in wrong thread. Skipping deletion of Tcl " \ + "interpreter (this will cause a memory leak)."); + PyErr_WriteUnraisable(PyErr_Occurred()); + PyErr_Clear(); + } else { + ENTER_TCL + Tcl_DeleteInterp(Tkapp_Interp(self)); + LEAVE_TCL + } PyObject_Del(self); Py_DECREF(tp); DisableEventHook();