You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
_thread.start_new_thread(): call sys.unraisablehook() to handle uncaught exceptions #81257
Python 3.8 now has 3 hooks for uncaught exceptions:
sys.excepthook()
sys.unraisablehook()
threading.excepthook()
_thread.start_new_thread() calls none of these hooks, but directly logs the exception.
I propose to modify it to reuse sys.unraisablehook(): see attached PR.
--
Using threading.excepthook() would be another option, but _thread.start_new_thread() is usually wrapped in threading.Thread.run() which already uses threading.excepthook(). It might be surprising to see two bugs from the same thread using threading.excepthook().
Moreover, right now, _thread is the "low-level" API to access threads: it doesn't acces threading. I'm not comfortable by adding an inter-dependency between _thread (low-level) and threading (high-level).
If threading.Thread.run() is correctly written, it should not raise an exception. In the worst case, threading.excepthook should handle the exception. _thread.start_new_thread() should never get an exception raised by threading if threading.excepthook does correctly its job.
With PR 13617, the function which raised the exception can be retrieved using unraisable.object.
If we used threading.excepthook, I don't see how we could pass this function into threading.ExceptHookArgs: ExceptHookArgs.thread is expected to be a threading.Thread object.
Serhiy Storchaka:
"If we want to support low-level threads created by start_new_thread() we should call [threading.]excepthook() from t_bootstrap instead of Thread._bootstrap_inner. Otherwise implementing excepthook() as a Thread method would be more convenient."
Output:
---
Unhandled exception in thread started by <function worker at 0x7fae27f212d0>
<_DummyThread(Dummy-1, started daemon 140385971111680)>
---
Ah right, sys.excepthook is called! But "Unhandled exception in thread started by <function worker at 0x7fae27f212d0>" line is always written into stderr, it cannot be avoided :-( And sys.excepthook doesn't provide access to the thread function which caused the issue.
_thread.start_new_thread() now logs uncaught exception raised by the
function using sys.unraisablehook(), rather than sys.excepthook(), so
the hook gets access to the function which raised the exception.
If threading.Thread.run() is correctly written, it should not raise an exception.
Since the error handling for threading.Thread.run() is written on Python there are many opportunities to get an exception in error handling: KeyboardInterrupt, MemoryError and, at the shutdown stage, maybe NameError, AttributeError or TypeError.
Since the error handling for threading.Thread.run() is written on Python there are many opportunities to get an exception in error handling: KeyboardInterrupt, MemoryError and, at the shutdown stage, maybe NameError, AttributeError or TypeError.
Sure, the risk is real, but I tried to minimize it.
NameError and AttributeError "should" not happen: I wrote _make_invoke_excepthook() to prevent name errors. Functions used to invoke threading.excepthook are "cached" in a private namespace. Moreover, the default hook is implemented in C also to reduce the risk of raising a new exception.
Anyway, if threading.excepthook() raises a second exception, sys.excepthook() is now called to handle it ;-) That's a Python 3.8 change. In Python 3.7 and older, new exception was handled by start_new_thread().
Activity
vstinner commentedon May 28, 2019
Python 3.8 now has 3 hooks for uncaught exceptions:
_thread.start_new_thread() calls none of these hooks, but directly logs the exception.
I propose to modify it to reuse sys.unraisablehook(): see attached PR.
--
Using threading.excepthook() would be another option, but _thread.start_new_thread() is usually wrapped in threading.Thread.run() which already uses threading.excepthook(). It might be surprising to see two bugs from the same thread using threading.excepthook().
Moreover, right now, _thread is the "low-level" API to access threads: it doesn't acces threading. I'm not comfortable by adding an inter-dependency between _thread (low-level) and threading (high-level).
If threading.Thread.run() is correctly written, it should not raise an exception. In the worst case, threading.excepthook should handle the exception. _thread.start_new_thread() should never get an exception raised by threading if threading.excepthook does correctly its job.
vstinner commentedon May 28, 2019
With PR 13617, the function which raised the exception can be retrieved using unraisable.object.
If we used threading.excepthook, I don't see how we could pass this function into threading.ExceptHookArgs: ExceptHookArgs.thread is expected to be a threading.Thread object.
vstinner commentedon May 28, 2019
This issue is a follow-up of:
Discussions around _thread.start_new_thead() exception:
Antoine Pitrou:
"""
From the doc: Handle uncaught :func:``Thread.run`` exception.
What if some C extension wants to report uncaught exceptions in C-created threads? Does it have to create a dummy Thread object?
For example, how about threads created by _thread.start_new_thread?
"""
#13515 (comment)
Serhiy Storchaka:
"If we want to support low-level threads created by start_new_thread() we should call [threading.]excepthook() from t_bootstrap instead of Thread._bootstrap_inner. Otherwise implementing excepthook() as a Thread method would be more convenient."
https://bugs.python.org/issue1230540#msg343748
lazka commentedon May 28, 2019
It calls sys.excepthook() currently:
vstinner commentedon May 28, 2019
Output:
---
Unhandled exception in thread started by <function worker at 0x7fae27f212d0>
<_DummyThread(Dummy-1, started daemon 140385971111680)>
---
Ah right, sys.excepthook is called! But "Unhandled exception in thread started by <function worker at 0x7fae27f212d0>" line is always written into stderr, it cannot be avoided :-( And sys.excepthook doesn't provide access to the thread function which caused the issue.
vstinner commentedon May 28, 2019
I updated my PR description:
_thread.start_new_thread() now logs uncaught exception raised by the
function using sys.unraisablehook(), rather than sys.excepthook(), so
the hook gets access to the function which raised the exception.
serhiy-storchaka commentedon May 28, 2019
Since the error handling for threading.Thread.run() is written on Python there are many opportunities to get an exception in error handling: KeyboardInterrupt, MemoryError and, at the shutdown stage, maybe NameError, AttributeError or TypeError.
vstinner commentedon May 28, 2019
Sure, the risk is real, but I tried to minimize it.
NameError and AttributeError "should" not happen: I wrote _make_invoke_excepthook() to prevent name errors. Functions used to invoke threading.excepthook are "cached" in a private namespace. Moreover, the default hook is implemented in C also to reduce the risk of raising a new exception.
Anyway, if threading.excepthook() raises a second exception, sys.excepthook() is now called to handle it ;-) That's a Python 3.8 change. In Python 3.7 and older, new exception was handled by start_new_thread().
vstinner commentedon May 29, 2019
New changeset 8b09500 by Victor Stinner in branch 'master':
bpo-37076: _thread.start_new_thread() calls _PyErr_WriteUnraisableMsg() (GH-13617)
8b09500
vstinner commentedon May 29, 2019
Thanks Serhiy for the review!