From 854dfaba828622fe6cbaadb85db0f1d974eaa3f1 Mon Sep 17 00:00:00 2001 From: Sergey Miryanov Date: Wed, 26 Feb 2025 21:48:05 +0500 Subject: [PATCH] gh-113148: Handle exceptions from threading.atexit functions --- Lib/test/test_interpreters/test_api.py | 37 ++++++++++++++++++++++++++ Lib/threading.py | 6 ++++- 2 files changed, 42 insertions(+), 1 deletion(-) diff --git a/Lib/test/test_interpreters/test_api.py b/Lib/test/test_interpreters/test_api.py index 01856d9bf67657..2fecc560320be2 100644 --- a/Lib/test/test_interpreters/test_api.py +++ b/Lib/test/test_interpreters/test_api.py @@ -599,6 +599,43 @@ def task(): self.assertEqual(os.read(r_interp, 1), FINISHED) + def test_subthreads_raise_exception_from_atexit(self): + r_interp, w_interp = self.pipe() + + FINISHED = b'F' + + prev_excepthook = threading.excepthook + interp = interpreters.create() + interp.exec(f"""if True: + from io import StringIO + import os + import threading + import time + + threading.excepthook = lambda args: args + + done = False + def notify_fini(): + global done + done = True + + def raise_exception(): + raise Exception('AtexitException') + threading._register_atexit(notify_fini) + threading._register_atexit(raise_exception) + + def task(): + while not done: + time.sleep(0.1) + os.write({w_interp}, {FINISHED!r}) + t = threading.Thread(target=task) + t.start() + """) + interp.close() + + self.assertEqual(threading.excepthook, prev_excepthook) + self.assertEqual(os.read(r_interp, 1), FINISHED) + def test_created_with_capi(self): script = dedent(f""" import {interpreters.__name__} as interpreters diff --git a/Lib/threading.py b/Lib/threading.py index da9cdf0b09d83c..04c84fdfa0c19f 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -1546,7 +1546,11 @@ def _shutdown(): # Call registered threading atexit functions before threads are joined. # Order is reversed, similar to atexit. for atexit_call in reversed(_threading_atexits): - atexit_call() + try: + atexit_call() + except: + th = current_thread() + th._invoke_excepthook(th) if _is_main_interpreter(): _main_thread._handle._set_done()