-
-
Notifications
You must be signed in to change notification settings - Fork 32.4k
bpo-1230540: Add threading.excepthook() #13515
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -5,7 +5,6 @@ | |
import _thread | ||
|
||
from time import monotonic as _time | ||
from traceback import format_exc as _format_exc | ||
from _weakrefset import WeakSet | ||
from itertools import islice as _islice, count as _count | ||
try: | ||
|
@@ -27,7 +26,8 @@ | |
'enumerate', 'main_thread', 'TIMEOUT_MAX', | ||
'Event', 'Lock', 'RLock', 'Semaphore', 'BoundedSemaphore', 'Thread', | ||
'Barrier', 'BrokenBarrierError', 'Timer', 'ThreadError', | ||
'setprofile', 'settrace', 'local', 'stack_size'] | ||
'setprofile', 'settrace', 'local', 'stack_size', | ||
'excepthook', 'ExceptHookArgs'] | ||
|
||
# Rename some stuff so "from threading import *" is safe | ||
_start_new_thread = _thread.start_new_thread | ||
|
@@ -752,14 +752,6 @@ class Thread: | |
""" | ||
|
||
_initialized = False | ||
# Need to store a reference to sys.exc_info for printing | ||
# out exceptions when a thread tries to use a global var. during interp. | ||
# shutdown and thus raises an exception about trying to perform some | ||
# operation on/with a NoneType | ||
_exc_info = _sys.exc_info | ||
# Keep sys.exc_clear too to clear the exception just before | ||
# allowing .join() to return. | ||
#XXX __exc_clear = _sys.exc_clear | ||
|
||
def __init__(self, group=None, target=None, name=None, | ||
args=(), kwargs=None, *, daemon=None): | ||
|
@@ -802,9 +794,9 @@ class is implemented. | |
self._started = Event() | ||
self._is_stopped = False | ||
self._initialized = True | ||
# sys.stderr is not stored in the class like | ||
# sys.exc_info since it can be changed between instances | ||
# Copy of sys.stderr used by self._invoke_excepthook() | ||
self._stderr = _sys.stderr | ||
self._invoke_excepthook = _make_invoke_excepthook() | ||
# For debugging and _after_fork() | ||
_dangling.add(self) | ||
|
||
|
@@ -929,47 +921,8 @@ def _bootstrap_inner(self): | |
|
||
try: | ||
self.run() | ||
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: | ||
print(( | ||
"Exception in thread " + self.name + | ||
" (most likely raised during interpreter shutdown):"), file=self._stderr) | ||
print(( | ||
"Traceback (most recent call last):"), file=self._stderr) | ||
while exc_tb: | ||
print(( | ||
' File "%s", line %s, in %s' % | ||
(exc_tb.tb_frame.f_code.co_filename, | ||
exc_tb.tb_lineno, | ||
exc_tb.tb_frame.f_code.co_name)), file=self._stderr) | ||
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 | ||
finally: | ||
# Prevent a race in | ||
# test_threading.test_no_refcycle_through_target when | ||
# the exception keeps the target alive past when we | ||
# assert that it's dead. | ||
#XXX self._exc_clear() | ||
pass | ||
self._invoke_excepthook(self) | ||
finally: | ||
with _active_limbo_lock: | ||
try: | ||
|
@@ -1163,6 +1116,104 @@ def getName(self): | |
def setName(self, name): | ||
self.name = name | ||
|
||
|
||
try: | ||
from _thread import (_excepthook as excepthook, | ||
_ExceptHookArgs as ExceptHookArgs) | ||
except ImportError: | ||
# Simple Python implementation if _thread._excepthook() is not available | ||
from traceback import print_exception as _print_exception | ||
from collections import namedtuple | ||
|
||
_ExceptHookArgs = namedtuple( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Is namedtuple for possible future extending a list of provided arguments? There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Exactly. See sys.unraisablehook: I used the same approach and today I added a new "err_msg" attribute ;-) |
||
'ExceptHookArgs', | ||
'exc_type exc_value exc_traceback thread') | ||
|
||
def ExceptHookArgs(args): | ||
return _ExceptHookArgs(*args) | ||
|
||
def excepthook(args, /): | ||
""" | ||
Handle uncaught Thread.run() exception. | ||
""" | ||
if args.exc_type == SystemExit: | ||
# silently ignore SystemExit | ||
return | ||
|
||
if _sys is not None and _sys.stderr is not None: | ||
stderr = _sys.stderr | ||
elif args.thread is not None: | ||
stderr = args.thread._stderr | ||
if stderr is None: | ||
# do nothing if sys.stderr is None and sys.stderr was None | ||
# when the thread was created | ||
return | ||
else: | ||
# do nothing if sys.stderr is None and args.thread is None | ||
return | ||
|
||
if args.thread is not None: | ||
name = args.thread.name | ||
else: | ||
name = get_ident() | ||
print(f"Exception in thread {name}:", | ||
file=stderr, flush=True) | ||
_print_exception(args.exc_type, args.exc_value, args.exc_traceback, | ||
file=stderr) | ||
stderr.flush() | ||
|
||
|
||
def _make_invoke_excepthook(): | ||
# Create a local namespace to ensure that variables remain alive | ||
# when _invoke_excepthook() is called, even if it is called late during | ||
# Python shutdown. It is mostly needed for daemon threads. | ||
|
||
old_excepthook = excepthook | ||
old_sys_excepthook = _sys.excepthook | ||
if old_excepthook is None: | ||
raise RuntimeError("threading.excepthook is None") | ||
if old_sys_excepthook is None: | ||
raise RuntimeError("sys.excepthook is None") | ||
|
||
sys_exc_info = _sys.exc_info | ||
local_print = print | ||
local_sys = _sys | ||
|
||
def invoke_excepthook(thread): | ||
global excepthook | ||
try: | ||
hook = excepthook | ||
if hook is None: | ||
hook = old_excepthook | ||
|
||
args = ExceptHookArgs([*sys_exc_info(), thread]) | ||
|
||
hook(args) | ||
except Exception as exc: | ||
exc.__suppress_context__ = True | ||
del exc | ||
|
||
if local_sys is not None and local_sys.stderr is not None: | ||
stderr = local_sys.stderr | ||
else: | ||
stderr = thread._stderr | ||
|
||
local_print("Exception in threading.excepthook:", | ||
file=stderr, flush=True) | ||
|
||
if local_sys is not None and local_sys.excepthook is not None: | ||
sys_excepthook = local_sys.excepthook | ||
else: | ||
sys_excepthook = old_sys_excepthook | ||
|
||
sys_excepthook(*sys_exc_info()) | ||
finally: | ||
# Break reference cycle (exception stored in a variable) | ||
args = None | ||
|
||
return invoke_excepthook | ||
|
||
|
||
# The timer class was contributed by Itamar Shtull-Trauring | ||
|
||
class Timer(Thread): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,3 @@ | ||
Add a new :func:`threading.excepthook` function which handles uncaught | ||
:meth:`threading.Thread.run` exception. It can be overridden to control how | ||
uncaught :meth:`threading.Thread.run` exceptions are handled. |
Uh oh!
There was an error while loading. Please reload this page.