Commit 8b095003 authored by Victor Stinner's avatar Victor Stinner Committed by GitHub

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.
parent b76302dd
...@@ -43,12 +43,22 @@ This module defines the following constants and functions: ...@@ -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()
......
...@@ -154,6 +154,24 @@ class ThreadRunningTests(BasicThreadTest): ...@@ -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):
......
: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.
...@@ -1002,25 +1002,15 @@ t_bootstrap(void *boot_raw) ...@@ -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);
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment