Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 16 additions & 6 deletions Doc/library/_thread.rst
Original file line number Diff line number Diff line change
Expand Up @@ -43,12 +43,22 @@ This module defines the following constants and functions:

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

Start a new thread and return its identifier. The thread executes the function
*function* with the argument list *args* (which must be a tuple). The optional
*kwargs* argument specifies a dictionary of keyword arguments. When the function
returns, the thread silently exits. When the function terminates with an
unhandled exception, a stack trace is printed and then the thread exits (but
other threads continue to run).
Start a new thread and return its identifier. The thread executes the
function *function* with the argument list *args* (which must be a tuple).
The optional *kwargs* argument specifies a dictionary of keyword arguments.

When the function returns, the thread silently exits.

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()
Expand Down
18 changes: 18 additions & 0 deletions Lib/test/test_thread.py
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,24 @@ def mywrite(self, *args):
started.acquire()
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:
def __init__(self, num_threads):
Expand Down
Original file line number Diff line number Diff line change
@@ -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.
18 changes: 4 additions & 14 deletions Modules/_threadmodule.c
Original file line number Diff line number Diff line change
Expand Up @@ -1002,25 +1002,15 @@ t_bootstrap(void *boot_raw)
res = PyObject_Call(boot->func, boot->args, boot->keyw);
if (res == NULL) {
if (PyErr_ExceptionMatches(PyExc_SystemExit))
/* SystemExit is ignored silently */
PyErr_Clear();
else {
PyObject *file;
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);
_PyErr_WriteUnraisableMsg("in thread started by", boot->func);
}
}
else
else {
Py_DECREF(res);
}
Py_DECREF(boot->func);
Py_DECREF(boot->args);
Py_XDECREF(boot->keyw);
Expand Down