[Python-checkins] bpo-37076: _thread.start_new_thread() calls _PyErr_WriteUnraisableMsg() (GH-13617)

Victor Stinner webhook-mailer at python.org
Tue May 28 20:58:02 EDT 2019


https://github.com/python/cpython/commit/8b09500345d998f3ff1e363a5210bc87f42ff306
commit: 8b09500345d998f3ff1e363a5210bc87f42ff306
branch: master
author: Victor Stinner <vstinner at redhat.com>
committer: GitHub <noreply at github.com>
date: 2019-05-29T02:57:56+02:00
summary:

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.

files:
A Misc/NEWS.d/next/Library/2019-05-28-12-17-10.bpo-37076.Bk2xOs.rst
M Doc/library/_thread.rst
M Lib/test/test_thread.py
M Modules/_threadmodule.c

diff --git a/Doc/library/_thread.rst b/Doc/library/_thread.rst
index a6ce945c7205..48d36e85c9e5 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 f4eb830cf6d7..f946f7bc8399 100644
--- a/Lib/test/test_thread.py
+++ b/Lib/test/test_thread.py
@@ -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):
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 000000000000..2773675cb5ad
--- /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 680e8ca7108c..2b1a98f81b1a 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);



More information about the Python-checkins mailing list