From 63143f0521e1effdfce803f2e31bb315f9ac2485 Mon Sep 17 00:00:00 2001 From: "E. Paine" <63801254+E-Paine@users.noreply.github.com> Date: Sat, 18 Jul 2020 14:33:50 +0100 Subject: [PATCH 01/13] Stop deallocation of Tkapp causing Python interpreter crash --- Modules/_tkinter.c | 13 +++++++++---- 1 file changed, 9 insertions(+), 4 deletions(-) diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 793c5e71548846..8e4ca5f281e12d 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -3040,10 +3040,15 @@ 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_WriteUnraisable(PyUnicode_FromString("Deallocation of Tkapp attempted in wrong thread. Skipping deletion of Tcl interpreter (this will cause a memory leak).")); \ + } else { + ENTER_TCL + Tcl_DeleteInterp(Tkapp_Interp(self)); + LEAVE_TCL + } PyObject_Del(self); Py_DECREF(tp); DisableEventHook(); From 853135a584f01f897c90ce4ce8572fc345e13c9b Mon Sep 17 00:00:00 2001 From: "blurb-it[bot]" <43283697+blurb-it[bot]@users.noreply.github.com> Date: Sat, 18 Jul 2020 13:44:25 +0000 Subject: [PATCH 02/13] =?UTF-8?q?=F0=9F=93=9C=F0=9F=A4=96=20Added=20by=20b?= =?UTF-8?q?lurb=5Fit.?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../NEWS.d/next/Library/2020-07-18-13-44-24.bpo-39093.yhmJSn.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2020-07-18-13-44-24.bpo-39093.yhmJSn.rst 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 00000000000000..860dc64313911b --- /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 From b34dfa137d65285f5ef5bf0e360f92caf5eabf04 Mon Sep 17 00:00:00 2001 From: "E. Paine" <63801254+E-Paine@users.noreply.github.com> Date: Sat, 18 Jul 2020 18:48:00 +0100 Subject: [PATCH 03/13] Added test for gc thread crash --- Lib/tkinter/test/test_tkinter/test_misc.py | 23 ++++++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index 1e089747a91ee5..ee97e4a26487e2 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 time import tkinter from test import support from tkinter.test.support import AbstractTkTest support.requires('gui') +def test_thread_gc(): + new_root = tkinter.Tk() + new_root.destroy() + threading.Thread(target=lambda obj: time.sleep(0.1), args=(new_root,)).start() + del new_root + time.sleep(0.6) + print("passed") + class MiscTest(AbstractTkTest, unittest.TestCase): def test_all(self): @@ -26,6 +38,17 @@ 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() + self.assertEqual(stdout, b"passed\n") + self.assertEqual(stderr, b"Exception ignored in: 'Deallocation of Tkapp " + b"attempted in wrong thread. Skipping deletion of Tcl " + b"interpreter (this will cause a memory leak).'\n") + def test_generated_names(self): t = tkinter.Toplevel(self.root) f = tkinter.Frame(t) From d084fc86ef513726a137c51519756759589a5e95 Mon Sep 17 00:00:00 2001 From: "E. Paine" <63801254+E-Paine@users.noreply.github.com> Date: Sat, 18 Jul 2020 20:27:27 +0100 Subject: [PATCH 04/13] Attempted test fixes --- Lib/tkinter/test/test_tkinter/test_misc.py | 9 +++++---- Modules/_tkinter.c | 7 ++++--- 2 files changed, 9 insertions(+), 7 deletions(-) diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index ee97e4a26487e2..2c87af4baf1eb0 100644 --- a/Lib/tkinter/test/test_tkinter/test_misc.py +++ b/Lib/tkinter/test/test_tkinter/test_misc.py @@ -12,7 +12,7 @@ def test_thread_gc(): new_root = tkinter.Tk() new_root.destroy() - threading.Thread(target=lambda obj: time.sleep(0.1), args=(new_root,)).start() + threading.Thread(target=lambda obj: time.sleep(0.2), args=(new_root,)).start() del new_root time.sleep(0.6) print("passed") @@ -44,10 +44,11 @@ def test_thread_gc(self): "test_misc.test_thread_gc()"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() - self.assertEqual(stdout, b"passed\n") - self.assertEqual(stderr, b"Exception ignored in: 'Deallocation of Tkapp " + self.assertEqual(stdout.strip(), b"passed") + self.assertEqual(stderr.strip(), + b"Exception ignored in: 'Deallocation of Tkapp " b"attempted in wrong thread. Skipping deletion of Tcl " - b"interpreter (this will cause a memory leak).'\n") + b"interpreter (this will cause a memory leak).'") def test_generated_names(self): t = tkinter.Toplevel(self.root) diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 8e4ca5f281e12d..9bf5de4492985e 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -3040,10 +3040,11 @@ static void Tkapp_Dealloc(PyObject *self) { PyObject *tp = (PyObject *) Py_TYPE(self); - if (((TkappObject *)self)->threaded && \ - ((TkappObject *)self)->thread_id != Tcl_GetCurrentThread()) { \ + if (((TkappObject *)self)->thread_id != Tcl_GetCurrentThread()) { // We cannot delete the interpreter in the wrong thread (bpo-39093) - PyErr_WriteUnraisable(PyUnicode_FromString("Deallocation of Tkapp attempted in wrong thread. Skipping deletion of Tcl interpreter (this will cause a memory leak).")); \ + PyErr_WriteUnraisable(PyUnicode_FromString("Deallocation of Tkapp " \ + "attempted in wrong thread. Skipping deletion of Tcl " \ + "interpreter (this will cause a memory leak).")); } else { ENTER_TCL Tcl_DeleteInterp(Tkapp_Interp(self)); From eb28228aa6f950f89162d3d9af9b27dec411b990 Mon Sep 17 00:00:00 2001 From: "E. Paine" <63801254+E-Paine@users.noreply.github.com> Date: Sat, 18 Jul 2020 21:09:03 +0100 Subject: [PATCH 05/13] Attempt to debug Ubuntu failure --- Lib/tkinter/test/test_tkinter/test_misc.py | 2 +- Modules/_tkinter.c | 3 ++- 2 files changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index 2c87af4baf1eb0..6b6f84d323d6fc 100644 --- a/Lib/tkinter/test/test_tkinter/test_misc.py +++ b/Lib/tkinter/test/test_tkinter/test_misc.py @@ -44,11 +44,11 @@ def test_thread_gc(self): "test_misc.test_thread_gc()"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() - self.assertEqual(stdout.strip(), b"passed") self.assertEqual(stderr.strip(), b"Exception ignored in: 'Deallocation of Tkapp " b"attempted in wrong thread. Skipping deletion of Tcl " b"interpreter (this will cause a memory leak).'") + self.assertEqual(stdout.strip(), b"passed") def test_generated_names(self): t = tkinter.Toplevel(self.root) diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 9bf5de4492985e..3a1445388cfbb8 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -3040,7 +3040,8 @@ static void Tkapp_Dealloc(PyObject *self) { PyObject *tp = (PyObject *) Py_TYPE(self); - if (((TkappObject *)self)->thread_id != Tcl_GetCurrentThread()) { + if (((TkappObject *)self)->threaded && \ + ((TkappObject *)self)->thread_id != Tcl_GetCurrentThread()) { // We cannot delete the interpreter in the wrong thread (bpo-39093) PyErr_WriteUnraisable(PyUnicode_FromString("Deallocation of Tkapp " \ "attempted in wrong thread. Skipping deletion of Tcl " \ From b58c49c7ae88321ae2b410f1094ef05e53a7f1c2 Mon Sep 17 00:00:00 2001 From: "E. Paine" <63801254+E-Paine@users.noreply.github.com> Date: Sun, 19 Jul 2020 10:38:02 +0100 Subject: [PATCH 06/13] Further attempt to debug Ubuntu failure --- Lib/tkinter/test/test_tkinter/test_misc.py | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index 6b6f84d323d6fc..9ae73ac7a66127 100644 --- a/Lib/tkinter/test/test_tkinter/test_misc.py +++ b/Lib/tkinter/test/test_tkinter/test_misc.py @@ -44,10 +44,17 @@ def test_thread_gc(self): "test_misc.test_thread_gc()"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() - self.assertEqual(stderr.strip(), - b"Exception ignored in: 'Deallocation of Tkapp " - b"attempted in wrong thread. Skipping deletion of Tcl " - b"interpreter (this will cause a memory leak).'") + try: + self.assertEqual(stderr.strip(), + b"Exception ignored in: 'Deallocation of Tkapp " + b"attempted in wrong thread. Skipping deletion of " + b"Tcl interpreter (this will cause a memory " + b"leak).'") + except AssertionError: + # If there is an error in the subprocess + # we need to be able to debug it + print(stderr, file=sys.stderr, end=" ") + raise self.assertEqual(stdout.strip(), b"passed") def test_generated_names(self): From 167ea911adb4d3d54c7a023e69497c2399fe622a Mon Sep 17 00:00:00 2001 From: "E. Paine" <63801254+E-Paine@users.noreply.github.com> Date: Sun, 19 Jul 2020 11:04:47 +0100 Subject: [PATCH 07/13] Fix bytes problem --- Lib/tkinter/test/test_tkinter/test_misc.py | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index 9ae73ac7a66127..5fee5a6ba31ade 100644 --- a/Lib/tkinter/test/test_tkinter/test_misc.py +++ b/Lib/tkinter/test/test_tkinter/test_misc.py @@ -44,8 +44,9 @@ def test_thread_gc(self): "test_misc.test_thread_gc()"], stdout=subprocess.PIPE, stderr=subprocess.PIPE) stdout, stderr = p.communicate() + stderr = stderr.strip() try: - self.assertEqual(stderr.strip(), + self.assertEqual(stderr, b"Exception ignored in: 'Deallocation of Tkapp " b"attempted in wrong thread. Skipping deletion of " b"Tcl interpreter (this will cause a memory " @@ -53,7 +54,7 @@ def test_thread_gc(self): except AssertionError: # If there is an error in the subprocess # we need to be able to debug it - print(stderr, file=sys.stderr, end=" ") + print(stderr.decode("utf-8"), file=sys.stderr) raise self.assertEqual(stdout.strip(), b"passed") From 93297b651193069457ac798f3950dbbc25726b2b Mon Sep 17 00:00:00 2001 From: "E. Paine" <63801254+E-Paine@users.noreply.github.com> Date: Sun, 19 Jul 2020 13:00:43 +0100 Subject: [PATCH 08/13] Attempted fix for Ubuntu failure --- Lib/tkinter/test/test_tkinter/test_misc.py | 9 ++++----- Modules/_tkinter.c | 6 ++++-- 2 files changed, 8 insertions(+), 7 deletions(-) diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index 5fee5a6ba31ade..c3cef0d3495285 100644 --- a/Lib/tkinter/test/test_tkinter/test_misc.py +++ b/Lib/tkinter/test/test_tkinter/test_misc.py @@ -46,11 +46,10 @@ def test_thread_gc(self): stdout, stderr = p.communicate() stderr = stderr.strip() try: - self.assertEqual(stderr, - b"Exception ignored in: 'Deallocation of Tkapp " - b"attempted in wrong thread. Skipping deletion of " - b"Tcl interpreter (this will cause a memory " - b"leak).'") + 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 diff --git a/Modules/_tkinter.c b/Modules/_tkinter.c index 3a1445388cfbb8..daeebd2ebb8d42 100644 --- a/Modules/_tkinter.c +++ b/Modules/_tkinter.c @@ -3043,9 +3043,11 @@ Tkapp_Dealloc(PyObject *self) if (((TkappObject *)self)->threaded && \ ((TkappObject *)self)->thread_id != Tcl_GetCurrentThread()) { // We cannot delete the interpreter in the wrong thread (bpo-39093) - PyErr_WriteUnraisable(PyUnicode_FromString("Deallocation of Tkapp " \ + PyErr_SetString(PyExc_RuntimeWarning, "Deallocation of Tkapp " \ "attempted in wrong thread. Skipping deletion of Tcl " \ - "interpreter (this will cause a memory leak).")); + "interpreter (this will cause a memory leak)."); + PyErr_WriteUnraisable(PyErr_Occurred()); + PyErr_Clear(); } else { ENTER_TCL Tcl_DeleteInterp(Tkapp_Interp(self)); From bea2cef27144ca15670d8d479bbf3adbb043c880 Mon Sep 17 00:00:00 2001 From: E-Paine <63801254+E-Paine@users.noreply.github.com> Date: Sun, 19 Jul 2020 14:09:19 +0100 Subject: [PATCH 09/13] Reduce subprocess duration This is mostly to determine if the asyncio failure is a temporary problem (this will force a re-test) or could (somehow) be a result of my changes. --- Lib/tkinter/test/test_tkinter/test_misc.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index c3cef0d3495285..3474b6ca5318c8 100644 --- a/Lib/tkinter/test/test_tkinter/test_misc.py +++ b/Lib/tkinter/test/test_tkinter/test_misc.py @@ -14,7 +14,7 @@ def test_thread_gc(): new_root.destroy() threading.Thread(target=lambda obj: time.sleep(0.2), args=(new_root,)).start() del new_root - time.sleep(0.6) + time.sleep(0.4) print("passed") class MiscTest(AbstractTkTest, unittest.TestCase): From a29f9ecde3cba1314cba090b0da04bf4e232ce50 Mon Sep 17 00:00:00 2001 From: "E. Paine" <63801254+E-Paine@users.noreply.github.com> Date: Sun, 19 Jul 2020 21:16:02 +0100 Subject: [PATCH 10/13] Changed subprocess to use threading events --- Lib/tkinter/test/test_tkinter/test_misc.py | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index 3474b6ca5318c8..2462181558a3f5 100644 --- a/Lib/tkinter/test/test_tkinter/test_misc.py +++ b/Lib/tkinter/test/test_tkinter/test_misc.py @@ -10,11 +10,16 @@ support.requires('gui') def test_thread_gc(): - new_root = tkinter.Tk() - new_root.destroy() - threading.Thread(target=lambda obj: time.sleep(0.2), args=(new_root,)).start() - del new_root - time.sleep(0.4) + def wait(obj): + ev1.wait() + ev2.set() + root = tkinter.Tk() + root.destroy() + ev1, ev2 = threading.Event(), threading.Event() + threading.Thread(target=wait, args=(root,)).start() + del root + ev1.set() + ev2.wait() print("passed") class MiscTest(AbstractTkTest, unittest.TestCase): From 7c1312dc560465a990c94d6115e9dc40b27bf429 Mon Sep 17 00:00:00 2001 From: "E. Paine" <63801254+E-Paine@users.noreply.github.com> Date: Sun, 19 Jul 2020 21:25:10 +0100 Subject: [PATCH 11/13] Removed un-needed import --- Lib/tkinter/test/test_tkinter/test_misc.py | 1 - 1 file changed, 1 deletion(-) diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index 2462181558a3f5..ea000ef442ef17 100644 --- a/Lib/tkinter/test/test_tkinter/test_misc.py +++ b/Lib/tkinter/test/test_tkinter/test_misc.py @@ -2,7 +2,6 @@ import subprocess import sys import threading -import time import tkinter from test import support from tkinter.test.support import AbstractTkTest From f15f0001e3a468d44ee73be83ac756fdba9ee381 Mon Sep 17 00:00:00 2001 From: "E. Paine" <63801254+E-Paine@users.noreply.github.com> Date: Mon, 20 Jul 2020 15:14:20 +0100 Subject: [PATCH 12/13] Removed redundant threading event --- Lib/tkinter/test/test_tkinter/test_misc.py | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index ea000ef442ef17..0ec1c75157ceda 100644 --- a/Lib/tkinter/test/test_tkinter/test_misc.py +++ b/Lib/tkinter/test/test_tkinter/test_misc.py @@ -10,15 +10,13 @@ def test_thread_gc(): def wait(obj): - ev1.wait() - ev2.set() + ev.wait() root = tkinter.Tk() root.destroy() - ev1, ev2 = threading.Event(), threading.Event() + ev = threading.Event() threading.Thread(target=wait, args=(root,)).start() del root - ev1.set() - ev2.wait() + ev.set() print("passed") class MiscTest(AbstractTkTest, unittest.TestCase): From 2f28f0ab74be324426586677ebea58c1c0223173 Mon Sep 17 00:00:00 2001 From: "E. Paine" <63801254+E-Paine@users.noreply.github.com> Date: Mon, 20 Jul 2020 19:55:51 +0100 Subject: [PATCH 13/13] Use lambda for thread --- Lib/tkinter/test/test_tkinter/test_misc.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/tkinter/test/test_tkinter/test_misc.py b/Lib/tkinter/test/test_tkinter/test_misc.py index 0ec1c75157ceda..69a08c22b52eaf 100644 --- a/Lib/tkinter/test/test_tkinter/test_misc.py +++ b/Lib/tkinter/test/test_tkinter/test_misc.py @@ -9,12 +9,10 @@ support.requires('gui') def test_thread_gc(): - def wait(obj): - ev.wait() root = tkinter.Tk() root.destroy() ev = threading.Event() - threading.Thread(target=wait, args=(root,)).start() + threading.Thread(target=lambda obj: ev.wait(), args=(root,)).start() del root ev.set() print("passed")