From 791da1cc264574f8f3e44570d0fce293f755fdf3 Mon Sep 17 00:00:00 2001 From: Victor Stinner Date: Mon, 14 Mar 2016 16:53:12 +0100 Subject: [PATCH] Fix Py_FatalError() if called without the GIL Issue #26558: If Py_FatalError() is called without the GIL, don't try to print the current exception, nor try to flush stdout and stderr: only dump the traceback of Python threads. --- Python/pylifecycle.c | 124 ++++++++++++++++++++++++------------------- 1 file changed, 70 insertions(+), 54 deletions(-) diff --git a/Python/pylifecycle.c b/Python/pylifecycle.c index 715a547d330..aaf58119e50 100644 --- a/Python/pylifecycle.c +++ b/Python/pylifecycle.c @@ -1267,61 +1267,11 @@ initstdio(void) } -/* Print the current exception (if an exception is set) with its traceback, - * or display the current Python stack. - * - * Don't call PyErr_PrintEx() and the except hook, because Py_FatalError() is - * called on catastrophic cases. */ - static void -_Py_PrintFatalError(int fd) +_Py_FatalError_DumpTracebacks(int fd) { - PyObject *ferr, *res; - PyObject *exception, *v, *tb; - int has_tb; PyThreadState *tstate; - PyErr_Fetch(&exception, &v, &tb); - if (exception == NULL) { - /* No current exception */ - goto display_stack; - } - - ferr = _PySys_GetObjectId(&PyId_stderr); - if (ferr == NULL || ferr == Py_None) { - /* sys.stderr is not set yet or set to None, - no need to try to display the exception */ - goto display_stack; - } - - PyErr_NormalizeException(&exception, &v, &tb); - if (tb == NULL) { - tb = Py_None; - Py_INCREF(tb); - } - PyException_SetTraceback(v, tb); - if (exception == NULL) { - /* PyErr_NormalizeException() failed */ - goto display_stack; - } - - has_tb = (tb != Py_None); - PyErr_Display(exception, v, tb); - Py_XDECREF(exception); - Py_XDECREF(v); - Py_XDECREF(tb); - - /* sys.stderr may be buffered: call sys.stderr.flush() */ - res = _PyObject_CallMethodId(ferr, &PyId_flush, ""); - if (res == NULL) - PyErr_Clear(); - else - Py_DECREF(res); - - if (has_tb) - return; - -display_stack: #ifdef WITH_THREAD /* PyGILState_GetThisThreadState() works even if the GIL was released */ tstate = PyGILState_GetThisThreadState(); @@ -1340,6 +1290,68 @@ display_stack: /* display the current Python stack */ _Py_DumpTracebackThreads(fd, tstate->interp, tstate); } + +/* Print the current exception (if an exception is set) with its traceback, + or display the current Python stack. + + Don't call PyErr_PrintEx() and the except hook, because Py_FatalError() is + called on catastrophic cases. + + Return 1 if the traceback was displayed, 0 otherwise. */ + +static int +_Py_FatalError_PrintExc(int fd) +{ + PyObject *ferr, *res; + PyObject *exception, *v, *tb; + int has_tb; + + if (PyThreadState_GET() == NULL) { + /* The GIL is released: trying to acquire it is likely to deadlock, + just give up. */ + return 0; + } + + PyErr_Fetch(&exception, &v, &tb); + if (exception == NULL) { + /* No current exception */ + return 0; + } + + ferr = _PySys_GetObjectId(&PyId_stderr); + if (ferr == NULL || ferr == Py_None) { + /* sys.stderr is not set yet or set to None, + no need to try to display the exception */ + return 0; + } + + PyErr_NormalizeException(&exception, &v, &tb); + if (tb == NULL) { + tb = Py_None; + Py_INCREF(tb); + } + PyException_SetTraceback(v, tb); + if (exception == NULL) { + /* PyErr_NormalizeException() failed */ + return 0; + } + + has_tb = (tb != Py_None); + PyErr_Display(exception, v, tb); + Py_XDECREF(exception); + Py_XDECREF(v); + Py_XDECREF(tb); + + /* sys.stderr may be buffered: call sys.stderr.flush() */ + res = _PyObject_CallMethodId(ferr, &PyId_flush, ""); + if (res == NULL) + PyErr_Clear(); + else + Py_DECREF(res); + + return has_tb; +} + /* Print fatal error message and abort */ void @@ -1365,10 +1377,14 @@ Py_FatalError(const char *msg) /* Print the exception (if an exception is set) with its traceback, * or display the current Python stack. */ - _Py_PrintFatalError(fd); + if (!_Py_FatalError_PrintExc(fd)) + _Py_FatalError_DumpTracebacks(fd); - /* Flush sys.stdout and sys.stderr */ - flush_std_files(); + /* Check if the current Python thread hold the GIL */ + if (PyThreadState_GET() != NULL) { + /* Flush sys.stdout and sys.stderr */ + flush_std_files(); + } /* The main purpose of faulthandler is to display the traceback. We already * did our best to display it. So faulthandler can now be disabled.