summaryrefslogtreecommitdiff
path: root/Modules
diff options
context:
space:
mode:
authorVictor Stinner <vstinner@redhat.com>2019-05-28 00:39:52 +0200
committerGitHub <noreply@github.com>2019-05-28 00:39:52 +0200
commitcd590a7cede156a4244e7cac61e4504e5344d842 (patch)
tree371aa076f7be6e942e904ebfa6aa6e7dbb2f0678 /Modules
parent23b4b697e5b6cc897696f9c0288c187d2d24bff2 (diff)
downloadcpython-git-cd590a7cede156a4244e7cac61e4504e5344d842.tar.gz
bpo-1230540: Add threading.excepthook() (GH-13515)
Add a new threading.excepthook() function which handles uncaught Thread.run() exception. It can be overridden to control how uncaught exceptions are handled. threading.ExceptHookArgs is not documented on purpose: it should not be used directly. * threading.excepthook() and threading.ExceptHookArgs. * Add _PyErr_Display(): similar to PyErr_Display(), but accept a 'file' parameter. * Add _thread._excepthook(): C implementation of the exception hook calling _PyErr_Display(). * Add _thread._ExceptHookArgs: structseq type. * Add threading._invoke_excepthook_wrapper() which handles the gory details to ensure that everything remains alive during Python shutdown. * Add unit tests.
Diffstat (limited to 'Modules')
-rw-r--r--Modules/_threadmodule.c156
1 files changed, 156 insertions, 0 deletions
diff --git a/Modules/_threadmodule.c b/Modules/_threadmodule.c
index fee25abe28..680e8ca710 100644
--- a/Modules/_threadmodule.c
+++ b/Modules/_threadmodule.c
@@ -3,6 +3,7 @@
/* Interface to Sjoerd's portable C thread library */
#include "Python.h"
+#include "pycore_pylifecycle.h"
#include "pycore_pystate.h"
#include "structmember.h" /* offsetof */
#include "pythread.h"
@@ -11,6 +12,7 @@ static PyObject *ThreadError;
static PyObject *str_dict;
_Py_IDENTIFIER(stderr);
+_Py_IDENTIFIER(flush);
/* Lock objects */
@@ -1309,6 +1311,147 @@ requiring allocation in multiples of the system memory page size\n\
(4 KiB pages are common; using multiples of 4096 for the stack size is\n\
the suggested approach in the absence of more specific information).");
+static int
+thread_excepthook_file(PyObject *file, PyObject *exc_type, PyObject *exc_value,
+ PyObject *exc_traceback, PyObject *thread)
+{
+ /* print(f"Exception in thread {thread.name}:", file=file) */
+ if (PyFile_WriteString("Exception in thread ", file) < 0) {
+ return -1;
+ }
+
+ PyObject *name = NULL;
+ if (thread != Py_None) {
+ name = PyObject_GetAttrString(thread, "name");
+ }
+ if (name != NULL) {
+ if (PyFile_WriteObject(name, file, Py_PRINT_RAW) < 0) {
+ Py_DECREF(name);
+ return -1;
+ }
+ Py_DECREF(name);
+ }
+ else {
+ PyErr_Clear();
+
+ unsigned long ident = PyThread_get_thread_ident();
+ PyObject *str = PyUnicode_FromFormat("%lu", ident);
+ if (str != NULL) {
+ if (PyFile_WriteObject(str, file, Py_PRINT_RAW) < 0) {
+ Py_DECREF(str);
+ return -1;
+ }
+ Py_DECREF(str);
+ }
+ else {
+ PyErr_Clear();
+
+ if (PyFile_WriteString("<failed to get thread name>", file) < 0) {
+ return -1;
+ }
+ }
+ }
+
+ if (PyFile_WriteString(":\n", file) < 0) {
+ return -1;
+ }
+
+ /* Display the traceback */
+ _PyErr_Display(file, exc_type, exc_value, exc_traceback);
+
+ /* Call file.flush() */
+ PyObject *res = _PyObject_CallMethodId(file, &PyId_flush, NULL);
+ if (!res) {
+ return -1;
+ }
+ Py_DECREF(res);
+
+ return 0;
+}
+
+
+PyDoc_STRVAR(ExceptHookArgs__doc__,
+"ExceptHookArgs\n\
+\n\
+Type used to pass arguments to threading.excepthook.");
+
+static PyTypeObject ExceptHookArgsType;
+
+static PyStructSequence_Field ExceptHookArgs_fields[] = {
+ {"exc_type", "Exception type"},
+ {"exc_value", "Exception value"},
+ {"exc_traceback", "Exception traceback"},
+ {"thread", "Thread"},
+ {0}
+};
+
+static PyStructSequence_Desc ExceptHookArgs_desc = {
+ .name = "_thread.ExceptHookArgs",
+ .doc = ExceptHookArgs__doc__,
+ .fields = ExceptHookArgs_fields,
+ .n_in_sequence = 4
+};
+
+
+static PyObject *
+thread_excepthook(PyObject *self, PyObject *args)
+{
+ if (Py_TYPE(args) != &ExceptHookArgsType) {
+ PyErr_SetString(PyExc_TypeError,
+ "_thread.excepthook argument type "
+ "must be ExceptHookArgs");
+ return NULL;
+ }
+
+ /* Borrowed reference */
+ PyObject *exc_type = PyStructSequence_GET_ITEM(args, 0);
+ if (exc_type == PyExc_SystemExit) {
+ /* silently ignore SystemExit */
+ Py_RETURN_NONE;
+ }
+
+ /* Borrowed references */
+ PyObject *exc_value = PyStructSequence_GET_ITEM(args, 1);
+ PyObject *exc_tb = PyStructSequence_GET_ITEM(args, 2);
+ PyObject *thread = PyStructSequence_GET_ITEM(args, 3);
+
+ PyObject *file = _PySys_GetObjectId(&PyId_stderr);
+ if (file == NULL || file == Py_None) {
+ if (thread == Py_None) {
+ /* do nothing if sys.stderr is None and thread is None */
+ Py_RETURN_NONE;
+ }
+
+ file = PyObject_GetAttrString(thread, "_stderr");
+ if (file == NULL) {
+ return NULL;
+ }
+ if (file == Py_None) {
+ Py_DECREF(file);
+ /* do nothing if sys.stderr is None and sys.stderr was None
+ when the thread was created */
+ Py_RETURN_NONE;
+ }
+ }
+ else {
+ Py_INCREF(file);
+ }
+
+ int res = thread_excepthook_file(file, exc_type, exc_value, exc_tb,
+ thread);
+ Py_DECREF(file);
+ if (res < 0) {
+ return NULL;
+ }
+
+ Py_RETURN_NONE;
+}
+
+PyDoc_STRVAR(excepthook_doc,
+"excepthook(exc_type, exc_value, exc_traceback, thread)\n\
+\n\
+Handle uncaught Thread.run() exception.");
+
static PyMethodDef thread_methods[] = {
{"start_new_thread", (PyCFunction)thread_PyThread_start_new_thread,
METH_VARARGS, start_new_doc},
@@ -1336,6 +1479,8 @@ static PyMethodDef thread_methods[] = {
METH_VARARGS, stack_size_doc},
{"_set_sentinel", thread__set_sentinel,
METH_NOARGS, _set_sentinel_doc},
+ {"_excepthook", thread_excepthook,
+ METH_O, excepthook_doc},
{NULL, NULL} /* sentinel */
};
@@ -1388,6 +1533,12 @@ PyInit__thread(void)
return NULL;
if (PyType_Ready(&RLocktype) < 0)
return NULL;
+ if (ExceptHookArgsType.tp_name == NULL) {
+ if (PyStructSequence_InitType2(&ExceptHookArgsType,
+ &ExceptHookArgs_desc) < 0) {
+ return NULL;
+ }
+ }
/* Create the module and add the functions */
m = PyModule_Create(&threadmodule);
@@ -1424,6 +1575,11 @@ PyInit__thread(void)
if (PyModule_AddObject(m, "_local", (PyObject *)&localtype) < 0)
return NULL;
+ Py_INCREF(&ExceptHookArgsType);
+ if (PyModule_AddObject(m, "_ExceptHookArgs",
+ (PyObject *)&ExceptHookArgsType) < 0)
+ return NULL;
+
interp->num_threads = 0;
str_dict = PyUnicode_InternFromString("__dict__");