Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions Lib/test/test_tkinter/test_misc.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,15 @@
import functools
import platform
import sys
import textwrap
import unittest
import weakref
import tkinter
from tkinter import TclError
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,
Expand Down Expand Up @@ -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
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
22 changes: 18 additions & 4 deletions Modules/_tkinter.c
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
Loading