bpo-37076: _thread.start_new_thread() calls _PyErr_WriteUnraisableMsg() (GH-13617)

_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.
This commit is contained in:
Victor Stinner 2019-05-29 02:57:56 +02:00 committed by GitHub
parent b76302ddd0
commit 8b09500345
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
4 changed files with 41 additions and 20 deletions

View File

@ -43,12 +43,22 @@ This module defines the following constants and functions:
.. function:: start_new_thread(function, args[, kwargs]) .. function:: start_new_thread(function, args[, kwargs])
Start a new thread and return its identifier. The thread executes the function Start a new thread and return its identifier. The thread executes the
*function* with the argument list *args* (which must be a tuple). The optional function *function* with the argument list *args* (which must be a tuple).
*kwargs* argument specifies a dictionary of keyword arguments. When the function The optional *kwargs* argument specifies a dictionary of keyword arguments.
returns, the thread silently exits. When the function terminates with an
unhandled exception, a stack trace is printed and then the thread exits (but When the function returns, the thread silently exits.
other threads continue to run).
When the function terminates with an unhandled exception,
:func:`sys.unraisablehook` is called to handle the exception. The *object*
attribute of the hook argument is *function*. By default, a stack trace is
printed and then the thread exits (but other threads continue to run).
When the function raises a :exc:`SystemExit` exception, it is silently
ignored.
.. versionchanged:: 3.8
:func:`sys.unraisablehook` is now used to handle unhandled exceptions.
.. function:: interrupt_main() .. function:: interrupt_main()

View File

@ -154,6 +154,24 @@ class ThreadRunningTests(BasicThreadTest):
started.acquire() started.acquire()
self.assertIn("Traceback", stderr.getvalue()) self.assertIn("Traceback", stderr.getvalue())
def test_unraisable_exception(self):
def task():
started.release()
raise ValueError("task failed")
started = thread.allocate_lock()
with support.catch_unraisable_exception() as cm:
with support.wait_threads_exit():
started.acquire()
thread.start_new_thread(task, ())
started.acquire()
self.assertEqual(str(cm.unraisable.exc_value), "task failed")
self.assertIs(cm.unraisable.object, task)
self.assertEqual(cm.unraisable.err_msg,
"Exception ignored in thread started by")
self.assertIsNotNone(cm.unraisable.exc_traceback)
class Barrier: class Barrier:
def __init__(self, num_threads): def __init__(self, num_threads):

View File

@ -0,0 +1,3 @@
:func:`_thread.start_new_thread` now logs uncaught exception raised by the
function using :func:`sys.unraisablehook`, rather than :func:`sys.excepthook`,
so the hook gets access to the function which raised the exception.

View File

@ -1002,25 +1002,15 @@ t_bootstrap(void *boot_raw)
res = PyObject_Call(boot->func, boot->args, boot->keyw); res = PyObject_Call(boot->func, boot->args, boot->keyw);
if (res == NULL) { if (res == NULL) {
if (PyErr_ExceptionMatches(PyExc_SystemExit)) if (PyErr_ExceptionMatches(PyExc_SystemExit))
/* SystemExit is ignored silently */
PyErr_Clear(); PyErr_Clear();
else { else {
PyObject *file; _PyErr_WriteUnraisableMsg("in thread started by", boot->func);
PyObject *exc, *value, *tb;
PySys_WriteStderr(
"Unhandled exception in thread started by ");
PyErr_Fetch(&exc, &value, &tb);
file = _PySys_GetObjectId(&PyId_stderr);
if (file != NULL && file != Py_None)
PyFile_WriteObject(boot->func, file, 0);
else
PyObject_Print(boot->func, stderr, 0);
PySys_WriteStderr("\n");
PyErr_Restore(exc, value, tb);
PyErr_PrintEx(0);
} }
} }
else else {
Py_DECREF(res); Py_DECREF(res);
}
Py_DECREF(boot->func); Py_DECREF(boot->func);
Py_DECREF(boot->args); Py_DECREF(boot->args);
Py_XDECREF(boot->keyw); Py_XDECREF(boot->keyw);