Skip to content

Commit 8b09500

Browse files
authored
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.
1 parent b76302d commit 8b09500

File tree

4 files changed

+41
-20
lines changed

4 files changed

+41
-20
lines changed

Doc/library/_thread.rst

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -43,12 +43,22 @@ This module defines the following constants and functions:
4343

4444
.. function:: start_new_thread(function, args[, kwargs])
4545

46-
Start a new thread and return its identifier. The thread executes the function
47-
*function* with the argument list *args* (which must be a tuple). The optional
48-
*kwargs* argument specifies a dictionary of keyword arguments. When the function
49-
returns, the thread silently exits. When the function terminates with an
50-
unhandled exception, a stack trace is printed and then the thread exits (but
51-
other threads continue to run).
46+
Start a new thread and return its identifier. The thread executes the
47+
function *function* with the argument list *args* (which must be a tuple).
48+
The optional *kwargs* argument specifies a dictionary of keyword arguments.
49+
50+
When the function returns, the thread silently exits.
51+
52+
When the function terminates with an unhandled exception,
53+
:func:`sys.unraisablehook` is called to handle the exception. The *object*
54+
attribute of the hook argument is *function*. By default, a stack trace is
55+
printed and then the thread exits (but other threads continue to run).
56+
57+
When the function raises a :exc:`SystemExit` exception, it is silently
58+
ignored.
59+
60+
.. versionchanged:: 3.8
61+
:func:`sys.unraisablehook` is now used to handle unhandled exceptions.
5262

5363

5464
.. function:: interrupt_main()

Lib/test/test_thread.py

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -154,6 +154,24 @@ def mywrite(self, *args):
154154
started.acquire()
155155
self.assertIn("Traceback", stderr.getvalue())
156156

157+
def test_unraisable_exception(self):
158+
def task():
159+
started.release()
160+
raise ValueError("task failed")
161+
162+
started = thread.allocate_lock()
163+
with support.catch_unraisable_exception() as cm:
164+
with support.wait_threads_exit():
165+
started.acquire()
166+
thread.start_new_thread(task, ())
167+
started.acquire()
168+
169+
self.assertEqual(str(cm.unraisable.exc_value), "task failed")
170+
self.assertIs(cm.unraisable.object, task)
171+
self.assertEqual(cm.unraisable.err_msg,
172+
"Exception ignored in thread started by")
173+
self.assertIsNotNone(cm.unraisable.exc_traceback)
174+
157175

158176
class Barrier:
159177
def __init__(self, num_threads):
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
:func:`_thread.start_new_thread` now logs uncaught exception raised by the
2+
function using :func:`sys.unraisablehook`, rather than :func:`sys.excepthook`,
3+
so the hook gets access to the function which raised the exception.

Modules/_threadmodule.c

Lines changed: 4 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1002,25 +1002,15 @@ t_bootstrap(void *boot_raw)
10021002
res = PyObject_Call(boot->func, boot->args, boot->keyw);
10031003
if (res == NULL) {
10041004
if (PyErr_ExceptionMatches(PyExc_SystemExit))
1005+
/* SystemExit is ignored silently */
10051006
PyErr_Clear();
10061007
else {
1007-
PyObject *file;
1008-
PyObject *exc, *value, *tb;
1009-
PySys_WriteStderr(
1010-
"Unhandled exception in thread started by ");
1011-
PyErr_Fetch(&exc, &value, &tb);
1012-
file = _PySys_GetObjectId(&PyId_stderr);
1013-
if (file != NULL && file != Py_None)
1014-
PyFile_WriteObject(boot->func, file, 0);
1015-
else
1016-
PyObject_Print(boot->func, stderr, 0);
1017-
PySys_WriteStderr("\n");
1018-
PyErr_Restore(exc, value, tb);
1019-
PyErr_PrintEx(0);
1008+
_PyErr_WriteUnraisableMsg("in thread started by", boot->func);
10201009
}
10211010
}
1022-
else
1011+
else {
10231012
Py_DECREF(res);
1013+
}
10241014
Py_DECREF(boot->func);
10251015
Py_DECREF(boot->args);
10261016
Py_XDECREF(boot->keyw);

0 commit comments

Comments
 (0)