From a2b603e524cac87e01adc7b801f2524f0b2d5454 Mon Sep 17 00:00:00 2001 From: Andrey Vlasovskikh Date: Thu, 2 Aug 2018 00:50:33 +0300 Subject: [PATCH] bpo-1230540: Invoke custom sys.excepthook for threads in threading Threads created via threading.Thread didn't invoke sys.excepthook if there was an uncaught exception. Since the docs for sys.excepthook are not specific about using it from threads and, in fact, this hook works for threads created via the _thread module, it makes sense to enable it the threading module as well. --- Lib/test/test_threading.py | 23 +++++++++++++++++++++++ Lib/threading.py | 36 +++++++++++++++++++----------------- 2 files changed, 42 insertions(+), 17 deletions(-) diff --git a/Lib/test/test_threading.py b/Lib/test/test_threading.py index db70dfa95df052..3282e58491941b 100644 --- a/Lib/test/test_threading.py +++ b/Lib/test/test_threading.py @@ -1106,6 +1106,29 @@ def run(self): # explicitly break the reference cycle to not leak a dangling thread thread.exc = None + def test_excepthook(self): + script = r"""if True: + import sys + import threading + + def f(): + raise Exception() + + def hook(*args): + # print('thread: %r' % (threading.current_thread(),)) + print('custom hook: %r' % (args,)) + + sys.excepthook = hook + + threading.Thread(target=f).start() + """ + rc, out, err = assert_python_ok("-c", script) + decoded_out = out.decode() + decoded_err = err.decode() + self.assertNotIn("Traceback", decoded_err) + self.assertIn("custom hook", decoded_out) + + class TimerTests(BaseTestCase): def setUp(self): diff --git a/Lib/threading.py b/Lib/threading.py index bb41456fb1410c..a7b4f7aade2fb2 100644 --- a/Lib/threading.py +++ b/Lib/threading.py @@ -918,19 +918,21 @@ def _bootstrap_inner(self): except SystemExit: pass except: - # If sys.stderr is no more (most likely from interpreter - # shutdown) use self._stderr. Otherwise still use sys (as in - # _sys) in case sys.stderr was redefined since the creation of - # self. - if _sys and _sys.stderr is not None: - print("Exception in thread %s:\n%s" % - (self.name, _format_exc()), file=_sys.stderr) - elif self._stderr is not None: - # Do the best job possible w/o a huge amt. of code to - # approximate a traceback (code ideas from - # Lib/traceback.py) - exc_type, exc_value, exc_tb = self._exc_info() - try: + exc_type, exc_value, exc_tb = self._exc_info() + try: + if _sys and _sys.excepthook != _sys.__excepthook__: + _sys.excepthook(exc_type, exc_value, exc_tb) + # If sys.stderr is no more (most likely from interpreter + # shutdown) use self._stderr. Otherwise still use sys (as in + # _sys) in case sys.stderr was redefined since the creation of + # self. + elif _sys and _sys.stderr is not None: + print("Exception in thread %s:\n%s" % + (self.name, _format_exc()), file=_sys.stderr) + elif self._stderr is not None: + # Do the best job possible w/o a huge amt. of code to + # approximate a traceback (code ideas from + # Lib/traceback.py) print(( "Exception in thread " + self.name + " (most likely raised during interpreter shutdown):"), file=self._stderr) @@ -945,10 +947,10 @@ def _bootstrap_inner(self): exc_tb = exc_tb.tb_next print(("%s: %s" % (exc_type, exc_value)), file=self._stderr) self._stderr.flush() - # Make sure that exc_tb gets deleted since it is a memory - # hog; deleting everything else is just for thoroughness - finally: - del exc_type, exc_value, exc_tb + # Make sure that exc_tb gets deleted since it is a memory + # hog; deleting everything else is just for thoroughness + finally: + del exc_type, exc_value, exc_tb finally: # Prevent a race in # test_threading.test_no_refcycle_through_target when