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.
This commit is contained in:
Victor Stinner 2019-05-28 00:39:52 +02:00 committed by GitHub
parent 23b4b697e5
commit cd590a7ced
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
9 changed files with 424 additions and 67 deletions

View file

@ -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__");