summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@redhat.com>2019-05-29 00:57:56 (GMT)
committerGitHub <noreply@github.com>2019-05-29 00:57:56 (GMT)
commit8b09500345d998f3ff1e363a5210bc87f42ff306 (patch)
tree3f03ee8d5f60fece00d48ae305253328413ae9d0
parentb76302ddd0896cb39ce69909349b53db6e7776e2 (diff)
downloadcpython-8b09500345d998f3ff1e363a5210bc87f42ff306.zip
cpython-8b09500345d998f3ff1e363a5210bc87f42ff306.tar.gz
cpython-8b09500345d998f3ff1e363a5210bc87f42ff306.tar.bz2
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.
-rw-r--r--Doc/library/_thread.rst22
-rw-r--r--Lib/test/test_thread.py18
-rw-r--r--Misc/NEWS.d/next/Library/2019-05-28-12-17-10.bpo-37076.Bk2xOs.rst3
-rw-r--r--Modules/_threadmodule.c18
4 files changed, 41 insertions, 20 deletions
diff --git a/Doc/library/_thread.rst b/Doc/library/_thread.rst
index a6ce945..48d36e8 100644
--- a/Doc/library/_thread.rst
+++ b/Doc/library/_thread.rst
@@ -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()
diff --git a/Lib/test/test_thread.py b/Lib/test/test_thread.py
index f4eb830..f946f7b 100644
--- a/Lib/test/test_thread.py
+++ b/Lib/test/test_thread.py
@@ -154,6 +154,24 @@ class ThreadRunningTests(BasicThreadTest):
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):
diff --git a/Misc/NEWS.d/next/Library/2019-05-28-12-17-10.bpo-37076.Bk2xOs.rst b/Misc/NEWS.d/next/Library/2019-05-28-12-17-10.bpo-37076.Bk2xOs.rst
new file mode 100644
index 0000000..2773675
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2019-05-28-12-17-10.bpo-37076.Bk2xOs.rst
@@ -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.
diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c
index 680e8ca..2b1a98f 100644
--- a/Modules/_threadmodule.c
+++ b/Modules/_threadmodule.c
@@ -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);