faulthandler now works in non-Python threads
Issue #26563: * Add _PyGILState_GetInterpreterStateUnsafe() function: the single PyInterpreterState used by this process' GILState implementation. * Enhance _Py_DumpTracebackThreads() to retrieve the interpreter state from autoInterpreterState in last resort. The function now accepts NULL for interp and current_tstate parameters. * test_faulthandler: fix a ResourceWarning when test is interrupted by CTRL+c
This commit is contained in:
parent
c36674a2c5
commit
861d9abfcf
|
@ -243,15 +243,23 @@ PyAPI_FUNC(void) PyGILState_Release(PyGILState_STATE);
|
|||
*/
|
||||
PyAPI_FUNC(PyThreadState *) PyGILState_GetThisThreadState(void);
|
||||
|
||||
/* Helper/diagnostic function - return 1 if the current thread
|
||||
* currently holds the GIL, 0 otherwise
|
||||
*/
|
||||
#ifndef Py_LIMITED_API
|
||||
/* Issue #26558: Flag to disable PyGILState_Check().
|
||||
If set, PyGILState_Check() always return 1. */
|
||||
If set to non-zero, PyGILState_Check() always return 1. */
|
||||
PyAPI_DATA(int) _PyGILState_check_enabled;
|
||||
|
||||
/* Helper/diagnostic function - return 1 if the current thread
|
||||
currently holds the GIL, 0 otherwise.
|
||||
|
||||
The function returns 1 if _PyGILState_check_enabled is non-zero. */
|
||||
PyAPI_FUNC(int) PyGILState_Check(void);
|
||||
|
||||
/* Unsafe function to get the single PyInterpreterState used by this process'
|
||||
GILState implementation.
|
||||
|
||||
Return NULL before _PyGILState_Init() is called and after _PyGILState_Fini()
|
||||
is called. */
|
||||
PyAPI_FUNC(PyInterpreterState *) _PyGILState_GetInterpreterStateUnsafe(void);
|
||||
#endif
|
||||
|
||||
#endif /* #ifdef WITH_THREAD */
|
||||
|
|
|
@ -53,19 +53,32 @@ PyAPI_DATA(void) _Py_DumpTraceback(
|
|||
PyThreadState *tstate);
|
||||
|
||||
/* Write the traceback of all threads into the file 'fd'. current_thread can be
|
||||
NULL. Return NULL on success, or an error message on error.
|
||||
NULL.
|
||||
|
||||
Return NULL on success, or an error message on error.
|
||||
|
||||
This function is written for debug purpose only. It calls
|
||||
_Py_DumpTraceback() for each thread, and so has the same limitations. It
|
||||
only write the traceback of the first 100 threads: write "..." if there are
|
||||
more threads.
|
||||
|
||||
If current_tstate is NULL, the function tries to get the Python thread state
|
||||
of the current thread. It is not an error if the function is unable to get
|
||||
the current Python thread state.
|
||||
|
||||
If interp is NULL, the function tries to get the interpreter state from
|
||||
the current Python thread state, or from
|
||||
_PyGILState_GetInterpreterStateUnsafe() in last resort.
|
||||
|
||||
It is better to pass NULL to interp and current_tstate, the function tries
|
||||
different options to retrieve these informations.
|
||||
|
||||
This function is signal safe. */
|
||||
|
||||
PyAPI_DATA(const char*) _Py_DumpTracebackThreads(
|
||||
int fd, PyInterpreterState *interp,
|
||||
PyThreadState *current_thread);
|
||||
|
||||
int fd,
|
||||
PyInterpreterState *interp,
|
||||
PyThreadState *current_tstate);
|
||||
|
||||
#ifndef Py_LIMITED_API
|
||||
|
||||
|
|
|
@ -58,8 +58,9 @@ class FaultHandlerTests(unittest.TestCase):
|
|||
pass_fds.append(fd)
|
||||
with support.SuppressCrashReport():
|
||||
process = script_helper.spawn_python('-c', code, pass_fds=pass_fds)
|
||||
stdout, stderr = process.communicate()
|
||||
exitcode = process.wait()
|
||||
with process:
|
||||
stdout, stderr = process.communicate()
|
||||
exitcode = process.wait()
|
||||
output = support.strip_python_stderr(stdout)
|
||||
output = output.decode('ascii', 'backslashreplace')
|
||||
if filename:
|
||||
|
@ -73,14 +74,11 @@ class FaultHandlerTests(unittest.TestCase):
|
|||
with open(fd, "rb", closefd=False) as fp:
|
||||
output = fp.read()
|
||||
output = output.decode('ascii', 'backslashreplace')
|
||||
output = re.sub('Current thread 0x[0-9a-f]+',
|
||||
'Current thread XXX',
|
||||
output)
|
||||
return output.splitlines(), exitcode
|
||||
|
||||
def check_fatal_error(self, code, line_number, name_regex,
|
||||
filename=None, all_threads=True, other_regex=None,
|
||||
fd=None):
|
||||
fd=None, know_current_thread=True):
|
||||
"""
|
||||
Check that the fault handler for fatal errors is enabled and check the
|
||||
traceback from the child process output.
|
||||
|
@ -88,19 +86,22 @@ class FaultHandlerTests(unittest.TestCase):
|
|||
Raise an error if the output doesn't match the expected format.
|
||||
"""
|
||||
if all_threads:
|
||||
header = 'Current thread XXX (most recent call first)'
|
||||
if know_current_thread:
|
||||
header = 'Current thread 0x[0-9a-f]+'
|
||||
else:
|
||||
header = 'Thread 0x[0-9a-f]+'
|
||||
else:
|
||||
header = 'Stack (most recent call first)'
|
||||
header = 'Stack'
|
||||
regex = """
|
||||
^Fatal Python error: {name}
|
||||
|
||||
{header}:
|
||||
{header} \(most recent call first\):
|
||||
File "<string>", line {lineno} in <module>
|
||||
"""
|
||||
regex = dedent(regex.format(
|
||||
lineno=line_number,
|
||||
name=name_regex,
|
||||
header=re.escape(header))).strip()
|
||||
header=header)).strip()
|
||||
if other_regex:
|
||||
regex += '|' + other_regex
|
||||
output, exitcode = self.get_output(code, filename=filename, fd=fd)
|
||||
|
@ -129,6 +130,17 @@ class FaultHandlerTests(unittest.TestCase):
|
|||
3,
|
||||
'Segmentation fault')
|
||||
|
||||
@unittest.skipIf(not HAVE_THREADS, 'need threads')
|
||||
def test_fatal_error_c_thread(self):
|
||||
self.check_fatal_error("""
|
||||
import faulthandler
|
||||
faulthandler.enable()
|
||||
faulthandler._fatal_error_c_thread()
|
||||
""",
|
||||
3,
|
||||
'in new thread',
|
||||
know_current_thread=False)
|
||||
|
||||
def test_sigabrt(self):
|
||||
self.check_fatal_error("""
|
||||
import faulthandler
|
||||
|
@ -465,7 +477,7 @@ class FaultHandlerTests(unittest.TestCase):
|
|||
File ".*threading.py", line [0-9]+ in _bootstrap_inner
|
||||
File ".*threading.py", line [0-9]+ in _bootstrap
|
||||
|
||||
Current thread XXX \(most recent call first\):
|
||||
Current thread 0x[0-9a-f]+ \(most recent call first\):
|
||||
File "<string>", line {lineno} in dump
|
||||
File "<string>", line 28 in <module>$
|
||||
"""
|
||||
|
@ -637,7 +649,7 @@ class FaultHandlerTests(unittest.TestCase):
|
|||
trace = '\n'.join(trace)
|
||||
if not unregister:
|
||||
if all_threads:
|
||||
regex = 'Current thread XXX \(most recent call first\):\n'
|
||||
regex = 'Current thread 0x[0-9a-f]+ \(most recent call first\):\n'
|
||||
else:
|
||||
regex = 'Stack \(most recent call first\):\n'
|
||||
regex = expected_traceback(14, 32, regex)
|
||||
|
|
|
@ -202,8 +202,9 @@ faulthandler_get_fileno(PyObject **file_ptr)
|
|||
static PyThreadState*
|
||||
get_thread_state(void)
|
||||
{
|
||||
PyThreadState *tstate = PyThreadState_Get();
|
||||
PyThreadState *tstate = _PyThreadState_UncheckedGet();
|
||||
if (tstate == NULL) {
|
||||
/* just in case but very unlikely... */
|
||||
PyErr_SetString(PyExc_RuntimeError,
|
||||
"unable to get the current thread state");
|
||||
return NULL;
|
||||
|
@ -234,11 +235,12 @@ faulthandler_dump_traceback(int fd, int all_threads,
|
|||
PyGILState_GetThisThreadState(). */
|
||||
tstate = PyGILState_GetThisThreadState();
|
||||
#else
|
||||
tstate = PyThreadState_Get();
|
||||
tstate = _PyThreadState_UncheckedGet();
|
||||
#endif
|
||||
|
||||
if (all_threads)
|
||||
_Py_DumpTracebackThreads(fd, interp, tstate);
|
||||
if (all_threads) {
|
||||
(void)_Py_DumpTracebackThreads(fd, NULL, tstate);
|
||||
}
|
||||
else {
|
||||
if (tstate != NULL)
|
||||
_Py_DumpTraceback(fd, tstate);
|
||||
|
@ -272,7 +274,7 @@ faulthandler_dump_traceback_py(PyObject *self,
|
|||
return NULL;
|
||||
|
||||
if (all_threads) {
|
||||
errmsg = _Py_DumpTracebackThreads(fd, tstate->interp, tstate);
|
||||
errmsg = _Py_DumpTracebackThreads(fd, NULL, tstate);
|
||||
if (errmsg != NULL) {
|
||||
PyErr_SetString(PyExc_RuntimeError, errmsg);
|
||||
return NULL;
|
||||
|
@ -469,7 +471,6 @@ faulthandler_thread(void *unused)
|
|||
{
|
||||
PyLockStatus st;
|
||||
const char* errmsg;
|
||||
PyThreadState *current;
|
||||
int ok;
|
||||
#if defined(HAVE_PTHREAD_SIGMASK) && !defined(HAVE_BROKEN_PTHREAD_SIGMASK)
|
||||
sigset_t set;
|
||||
|
@ -489,12 +490,9 @@ faulthandler_thread(void *unused)
|
|||
/* Timeout => dump traceback */
|
||||
assert(st == PY_LOCK_FAILURE);
|
||||
|
||||
/* get the thread holding the GIL, NULL if no thread hold the GIL */
|
||||
current = _PyThreadState_UncheckedGet();
|
||||
|
||||
_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, NULL);
|
||||
ok = (errmsg == NULL);
|
||||
|
||||
if (thread.exit)
|
||||
|
@ -894,7 +892,7 @@ static PyObject *
|
|||
faulthandler_sigsegv(PyObject *self, PyObject *args)
|
||||
{
|
||||
int release_gil = 0;
|
||||
if (!PyArg_ParseTuple(args, "|i:_read_null", &release_gil))
|
||||
if (!PyArg_ParseTuple(args, "|i:_sigsegv", &release_gil))
|
||||
return NULL;
|
||||
|
||||
if (release_gil) {
|
||||
|
@ -907,6 +905,49 @@ faulthandler_sigsegv(PyObject *self, PyObject *args)
|
|||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
#ifdef WITH_THREAD
|
||||
static void
|
||||
faulthandler_fatal_error_thread(void *plock)
|
||||
{
|
||||
PyThread_type_lock *lock = (PyThread_type_lock *)plock;
|
||||
|
||||
Py_FatalError("in new thread");
|
||||
|
||||
/* notify the caller that we are done */
|
||||
PyThread_release_lock(lock);
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
faulthandler_fatal_error_c_thread(PyObject *self, PyObject *args)
|
||||
{
|
||||
long thread;
|
||||
PyThread_type_lock lock;
|
||||
|
||||
faulthandler_suppress_crash_report();
|
||||
|
||||
lock = PyThread_allocate_lock();
|
||||
if (lock == NULL)
|
||||
return PyErr_NoMemory();
|
||||
|
||||
PyThread_acquire_lock(lock, WAIT_LOCK);
|
||||
|
||||
thread = PyThread_start_new_thread(faulthandler_fatal_error_thread, lock);
|
||||
if (thread == -1) {
|
||||
PyThread_free_lock(lock);
|
||||
PyErr_SetString(PyExc_RuntimeError, "unable to start the thread");
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/* wait until the thread completes: it will never occur, since Py_FatalError()
|
||||
exits the process immedialty. */
|
||||
PyThread_acquire_lock(lock, WAIT_LOCK);
|
||||
PyThread_release_lock(lock);
|
||||
PyThread_free_lock(lock);
|
||||
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
#endif
|
||||
|
||||
static PyObject *
|
||||
faulthandler_sigfpe(PyObject *self, PyObject *args)
|
||||
{
|
||||
|
@ -1065,6 +1106,11 @@ static PyMethodDef module_methods[] = {
|
|||
"a SIGSEGV or SIGBUS signal depending on the platform")},
|
||||
{"_sigsegv", faulthandler_sigsegv, METH_VARARGS,
|
||||
PyDoc_STR("_sigsegv(release_gil=False): raise a SIGSEGV signal")},
|
||||
#ifdef WITH_THREAD
|
||||
{"_fatal_error_c_thread", faulthandler_fatal_error_c_thread, METH_NOARGS,
|
||||
PyDoc_STR("fatal_error_c_thread(): "
|
||||
"call Py_FatalError() in a new C thread.")},
|
||||
#endif
|
||||
{"_sigabrt", faulthandler_sigabrt, METH_NOARGS,
|
||||
PyDoc_STR("_sigabrt(): raise a SIGABRT signal")},
|
||||
{"_sigfpe", (PyCFunction)faulthandler_sigfpe, METH_NOARGS,
|
||||
|
|
|
@ -1275,25 +1275,11 @@ initstdio(void)
|
|||
static void
|
||||
_Py_FatalError_DumpTracebacks(int fd)
|
||||
{
|
||||
PyThreadState *tstate;
|
||||
|
||||
#ifdef WITH_THREAD
|
||||
/* PyGILState_GetThisThreadState() works even if the GIL was released */
|
||||
tstate = PyGILState_GetThisThreadState();
|
||||
#else
|
||||
tstate = PyThreadState_GET();
|
||||
#endif
|
||||
if (tstate == NULL) {
|
||||
/* _Py_DumpTracebackThreads() requires the thread state to display
|
||||
* frames */
|
||||
return;
|
||||
}
|
||||
|
||||
fputc('\n', stderr);
|
||||
fflush(stderr);
|
||||
|
||||
/* display the current Python stack */
|
||||
_Py_DumpTracebackThreads(fd, tstate->interp, tstate);
|
||||
_Py_DumpTracebackThreads(fd, NULL, NULL);
|
||||
}
|
||||
|
||||
/* Print the current exception (if an exception is set) with its traceback,
|
||||
|
|
|
@ -714,6 +714,12 @@ _PyGILState_Init(PyInterpreterState *i, PyThreadState *t)
|
|||
_PyGILState_NoteThreadState(t);
|
||||
}
|
||||
|
||||
PyInterpreterState *
|
||||
_PyGILState_GetInterpreterStateUnsafe(void)
|
||||
{
|
||||
return autoInterpreterState;
|
||||
}
|
||||
|
||||
void
|
||||
_PyGILState_Fini(void)
|
||||
{
|
||||
|
|
|
@ -707,11 +707,56 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current)
|
|||
handlers if signals were received. */
|
||||
const char*
|
||||
_Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
|
||||
PyThreadState *current_thread)
|
||||
PyThreadState *current_tstate)
|
||||
{
|
||||
PyThreadState *tstate;
|
||||
unsigned int nthreads;
|
||||
|
||||
#ifdef WITH_THREAD
|
||||
if (current_tstate == NULL) {
|
||||
/* _Py_DumpTracebackThreads() is called from signal handlers by
|
||||
faulthandler.
|
||||
|
||||
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(). */
|
||||
current_tstate = PyGILState_GetThisThreadState();
|
||||
}
|
||||
|
||||
if (interp == NULL) {
|
||||
if (current_tstate == NULL) {
|
||||
interp = _PyGILState_GetInterpreterStateUnsafe();
|
||||
if (interp == NULL) {
|
||||
/* We need the interpreter state to get Python threads */
|
||||
return "unable to get the interpreter state";
|
||||
}
|
||||
}
|
||||
else {
|
||||
interp = current_tstate->interp;
|
||||
}
|
||||
}
|
||||
#else
|
||||
if (current_tstate == NULL) {
|
||||
/* Call _PyThreadState_UncheckedGet() instead of PyThreadState_Get()
|
||||
to not fail with a fatal error if the thread state is NULL. */
|
||||
current_thread = _PyThreadState_UncheckedGet();
|
||||
}
|
||||
|
||||
if (interp == NULL) {
|
||||
if (current_tstate == NULL) {
|
||||
/* We need the interpreter state to get Python threads */
|
||||
return "unable to get the interpreter state";
|
||||
}
|
||||
interp = current_tstate->interp;
|
||||
}
|
||||
#endif
|
||||
assert(interp != NULL);
|
||||
|
||||
/* Get the current interpreter from the current thread */
|
||||
tstate = PyInterpreterState_ThreadHead(interp);
|
||||
if (tstate == NULL)
|
||||
|
@ -729,7 +774,7 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
|
|||
PUTS(fd, "...\n");
|
||||
break;
|
||||
}
|
||||
write_thread_id(fd, tstate, tstate == current_thread);
|
||||
write_thread_id(fd, tstate, tstate == current_tstate);
|
||||
dump_traceback(fd, tstate, 0);
|
||||
tstate = PyThreadState_Next(tstate);
|
||||
nthreads++;
|
||||
|
|
Loading…
Reference in New Issue