Issue #23836: Fix the faulthandler module to handle reentrant calls

to its signal handlers.

Use also _Py_write_noraise() instead of write() to retry write() if it is
interrupted by a signal (fail with EINTR).

faulthandler.dump_traceback() also calls PyErr_CheckSignals() to call the
Python signal handler if a signal was received.
This commit is contained in:
Victor Stinner 2015-04-01 18:48:58 +02:00
parent 97f86b82b7
commit c7489a5595
2 changed files with 48 additions and 40 deletions

View File

@ -13,6 +13,9 @@ Core and Builtins
Library Library
------- -------
- Issue #23836: Fix the faulthandler module to handle reentrant calls to
its signal handlers.
- Issue #23838: linecache now clears the cache and returns an empty result on - Issue #23838: linecache now clears the cache and returns an empty result on
MemoryError. MemoryError.

View File

@ -28,9 +28,7 @@
# define FAULTHANDLER_USER # define FAULTHANDLER_USER
#endif #endif
/* cast size_t to int because write() takes an int on Windows #define PUTS(fd, str) _Py_write_noraise(fd, str, strlen(str))
(anyway, the length is smaller than 30 characters) */
#define PUTS(fd, str) write(fd, str, (int)strlen(str))
_Py_IDENTIFIER(enable); _Py_IDENTIFIER(enable);
_Py_IDENTIFIER(fileno); _Py_IDENTIFIER(fileno);
@ -213,6 +211,42 @@ get_thread_state(void)
return tstate; return tstate;
} }
static void
faulthandler_dump_traceback(int fd, int all_threads,
PyInterpreterState *interp)
{
static volatile int reentrant = 0;
PyThreadState *tstate;
if (reentrant)
return;
reentrant = 1;
#ifdef WITH_THREAD
/* SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL are synchronous signals and
are thus delivered to the thread that caused the fault. Get the Python
thread state of the current thread.
PyThreadState_Get() doesn't give the state of the thread that caused the
fault if the thread released the GIL, and so this function cannot be
used. Read the thread local storage (TLS) instead: call
PyGILState_GetThisThreadState(). */
tstate = PyGILState_GetThisThreadState();
#else
tstate = PyThreadState_Get();
#endif
if (all_threads)
_Py_DumpTracebackThreads(fd, interp, tstate);
else {
if (tstate != NULL)
_Py_DumpTraceback(fd, tstate);
}
reentrant = 0;
}
static PyObject* static PyObject*
faulthandler_dump_traceback_py(PyObject *self, faulthandler_dump_traceback_py(PyObject *self,
PyObject *args, PyObject *kwargs) PyObject *args, PyObject *kwargs)
@ -247,6 +281,10 @@ faulthandler_dump_traceback_py(PyObject *self,
else { else {
_Py_DumpTraceback(fd, tstate); _Py_DumpTraceback(fd, tstate);
} }
if (PyErr_CheckSignals())
return NULL;
Py_RETURN_NONE; Py_RETURN_NONE;
} }
@ -270,7 +308,6 @@ faulthandler_fatal_error(int signum)
const int fd = fatal_error.fd; const int fd = fatal_error.fd;
unsigned int i; unsigned int i;
fault_handler_t *handler = NULL; fault_handler_t *handler = NULL;
PyThreadState *tstate;
int save_errno = errno; int save_errno = errno;
if (!fatal_error.enabled) if (!fatal_error.enabled)
@ -298,26 +335,8 @@ faulthandler_fatal_error(int signum)
PUTS(fd, handler->name); PUTS(fd, handler->name);
PUTS(fd, "\n\n"); PUTS(fd, "\n\n");
#ifdef WITH_THREAD faulthandler_dump_traceback(fd, fatal_error.all_threads,
/* SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL are synchronous signals and fatal_error.interp);
are thus delivered to the thread that caused the fault. Get the Python
thread state of the current thread.
PyThreadState_Get() doesn't give the state of the thread that caused the
fault if the thread released the GIL, and so this function cannot be
used. Read the thread local storage (TLS) instead: call
PyGILState_GetThisThreadState(). */
tstate = PyGILState_GetThisThreadState();
#else
tstate = PyThreadState_Get();
#endif
if (fatal_error.all_threads)
_Py_DumpTracebackThreads(fd, fatal_error.interp, tstate);
else {
if (tstate != NULL)
_Py_DumpTraceback(fd, tstate);
}
errno = save_errno; errno = save_errno;
#ifdef MS_WINDOWS #ifdef MS_WINDOWS
@ -474,7 +493,7 @@ faulthandler_thread(void *unused)
/* get the thread holding the GIL, NULL if no thread hold the GIL */ /* get the thread holding the GIL, NULL if no thread hold the GIL */
current = (PyThreadState*)_Py_atomic_load_relaxed(&_PyThreadState_Current); current = (PyThreadState*)_Py_atomic_load_relaxed(&_PyThreadState_Current);
write(thread.fd, thread.header, (int)thread.header_len); _Py_write_noraise(thread.fd, thread.header, (int)thread.header_len);
errmsg = _Py_DumpTracebackThreads(thread.fd, thread.interp, current); errmsg = _Py_DumpTracebackThreads(thread.fd, thread.interp, current);
ok = (errmsg == NULL); ok = (errmsg == NULL);
@ -660,28 +679,14 @@ static void
faulthandler_user(int signum) faulthandler_user(int signum)
{ {
user_signal_t *user; user_signal_t *user;
PyThreadState *tstate;
int save_errno = errno; int save_errno = errno;
user = &user_signals[signum]; user = &user_signals[signum];
if (!user->enabled) if (!user->enabled)
return; return;
#ifdef WITH_THREAD faulthandler_dump_traceback(user->fd, user->all_threads, user->interp);
/* PyThreadState_Get() doesn't give the state of the current thread if
the thread doesn't hold the GIL. Read the thread local storage (TLS)
instead: call PyGILState_GetThisThreadState(). */
tstate = PyGILState_GetThisThreadState();
#else
tstate = PyThreadState_Get();
#endif
if (user->all_threads)
_Py_DumpTracebackThreads(user->fd, user->interp, tstate);
else {
if (tstate != NULL)
_Py_DumpTraceback(user->fd, tstate);
}
#ifdef HAVE_SIGACTION #ifdef HAVE_SIGACTION
if (user->chain) { if (user->chain) {
(void)sigaction(signum, &user->previous, NULL); (void)sigaction(signum, &user->previous, NULL);