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:
parent
b76302ddd0
commit
8b09500345
|
@ -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):
|
||||||
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):
|
||||||
|
|
|
@ -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.
|
|
@ -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);
|
||||||
|
|
Loading…
Reference in New Issue