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);
|
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
|
#ifndef Py_LIMITED_API
|
||||||
/* Issue #26558: Flag to disable PyGILState_Check().
|
/* 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;
|
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);
|
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
|
||||||
|
|
||||||
#endif /* #ifdef WITH_THREAD */
|
#endif /* #ifdef WITH_THREAD */
|
||||||
|
|
|
@ -53,19 +53,32 @@ PyAPI_DATA(void) _Py_DumpTraceback(
|
||||||
PyThreadState *tstate);
|
PyThreadState *tstate);
|
||||||
|
|
||||||
/* Write the traceback of all threads into the file 'fd'. current_thread can be
|
/* 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
|
This function is written for debug purpose only. It calls
|
||||||
_Py_DumpTraceback() for each thread, and so has the same limitations. It
|
_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
|
only write the traceback of the first 100 threads: write "..." if there are
|
||||||
more threads.
|
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. */
|
This function is signal safe. */
|
||||||
|
|
||||||
PyAPI_DATA(const char*) _Py_DumpTracebackThreads(
|
PyAPI_DATA(const char*) _Py_DumpTracebackThreads(
|
||||||
int fd, PyInterpreterState *interp,
|
int fd,
|
||||||
PyThreadState *current_thread);
|
PyInterpreterState *interp,
|
||||||
|
PyThreadState *current_tstate);
|
||||||
|
|
||||||
#ifndef Py_LIMITED_API
|
#ifndef Py_LIMITED_API
|
||||||
|
|
||||||
|
|
|
@ -58,8 +58,9 @@ class FaultHandlerTests(unittest.TestCase):
|
||||||
pass_fds.append(fd)
|
pass_fds.append(fd)
|
||||||
with support.SuppressCrashReport():
|
with support.SuppressCrashReport():
|
||||||
process = script_helper.spawn_python('-c', code, pass_fds=pass_fds)
|
process = script_helper.spawn_python('-c', code, pass_fds=pass_fds)
|
||||||
stdout, stderr = process.communicate()
|
with process:
|
||||||
exitcode = process.wait()
|
stdout, stderr = process.communicate()
|
||||||
|
exitcode = process.wait()
|
||||||
output = support.strip_python_stderr(stdout)
|
output = support.strip_python_stderr(stdout)
|
||||||
output = output.decode('ascii', 'backslashreplace')
|
output = output.decode('ascii', 'backslashreplace')
|
||||||
if filename:
|
if filename:
|
||||||
|
@ -73,14 +74,11 @@ class FaultHandlerTests(unittest.TestCase):
|
||||||
with open(fd, "rb", closefd=False) as fp:
|
with open(fd, "rb", closefd=False) as fp:
|
||||||
output = fp.read()
|
output = fp.read()
|
||||||
output = output.decode('ascii', 'backslashreplace')
|
output = output.decode('ascii', 'backslashreplace')
|
||||||
output = re.sub('Current thread 0x[0-9a-f]+',
|
|
||||||
'Current thread XXX',
|
|
||||||
output)
|
|
||||||
return output.splitlines(), exitcode
|
return output.splitlines(), exitcode
|
||||||
|
|
||||||
def check_fatal_error(self, code, line_number, name_regex,
|
def check_fatal_error(self, code, line_number, name_regex,
|
||||||
filename=None, all_threads=True, other_regex=None,
|
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
|
Check that the fault handler for fatal errors is enabled and check the
|
||||||
traceback from the child process output.
|
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.
|
Raise an error if the output doesn't match the expected format.
|
||||||
"""
|
"""
|
||||||
if all_threads:
|
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:
|
else:
|
||||||
header = 'Stack (most recent call first)'
|
header = 'Stack'
|
||||||
regex = """
|
regex = """
|
||||||
^Fatal Python error: {name}
|
^Fatal Python error: {name}
|
||||||
|
|
||||||
{header}:
|
{header} \(most recent call first\):
|
||||||
File "<string>", line {lineno} in <module>
|
File "<string>", line {lineno} in <module>
|
||||||
"""
|
"""
|
||||||
regex = dedent(regex.format(
|
regex = dedent(regex.format(
|
||||||
lineno=line_number,
|
lineno=line_number,
|
||||||
name=name_regex,
|
name=name_regex,
|
||||||
header=re.escape(header))).strip()
|
header=header)).strip()
|
||||||
if other_regex:
|
if other_regex:
|
||||||
regex += '|' + other_regex
|
regex += '|' + other_regex
|
||||||
output, exitcode = self.get_output(code, filename=filename, fd=fd)
|
output, exitcode = self.get_output(code, filename=filename, fd=fd)
|
||||||
|
@ -129,6 +130,17 @@ class FaultHandlerTests(unittest.TestCase):
|
||||||
3,
|
3,
|
||||||
'Segmentation fault')
|
'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):
|
def test_sigabrt(self):
|
||||||
self.check_fatal_error("""
|
self.check_fatal_error("""
|
||||||
import faulthandler
|
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_inner
|
||||||
File ".*threading.py", line [0-9]+ in _bootstrap
|
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 {lineno} in dump
|
||||||
File "<string>", line 28 in <module>$
|
File "<string>", line 28 in <module>$
|
||||||
"""
|
"""
|
||||||
|
@ -637,7 +649,7 @@ class FaultHandlerTests(unittest.TestCase):
|
||||||
trace = '\n'.join(trace)
|
trace = '\n'.join(trace)
|
||||||
if not unregister:
|
if not unregister:
|
||||||
if all_threads:
|
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:
|
else:
|
||||||
regex = 'Stack \(most recent call first\):\n'
|
regex = 'Stack \(most recent call first\):\n'
|
||||||
regex = expected_traceback(14, 32, regex)
|
regex = expected_traceback(14, 32, regex)
|
||||||
|
|
|
@ -202,8 +202,9 @@ faulthandler_get_fileno(PyObject **file_ptr)
|
||||||
static PyThreadState*
|
static PyThreadState*
|
||||||
get_thread_state(void)
|
get_thread_state(void)
|
||||||
{
|
{
|
||||||
PyThreadState *tstate = PyThreadState_Get();
|
PyThreadState *tstate = _PyThreadState_UncheckedGet();
|
||||||
if (tstate == NULL) {
|
if (tstate == NULL) {
|
||||||
|
/* just in case but very unlikely... */
|
||||||
PyErr_SetString(PyExc_RuntimeError,
|
PyErr_SetString(PyExc_RuntimeError,
|
||||||
"unable to get the current thread state");
|
"unable to get the current thread state");
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -234,11 +235,12 @@ faulthandler_dump_traceback(int fd, int all_threads,
|
||||||
PyGILState_GetThisThreadState(). */
|
PyGILState_GetThisThreadState(). */
|
||||||
tstate = PyGILState_GetThisThreadState();
|
tstate = PyGILState_GetThisThreadState();
|
||||||
#else
|
#else
|
||||||
tstate = PyThreadState_Get();
|
tstate = _PyThreadState_UncheckedGet();
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
if (all_threads)
|
if (all_threads) {
|
||||||
_Py_DumpTracebackThreads(fd, interp, tstate);
|
(void)_Py_DumpTracebackThreads(fd, NULL, tstate);
|
||||||
|
}
|
||||||
else {
|
else {
|
||||||
if (tstate != NULL)
|
if (tstate != NULL)
|
||||||
_Py_DumpTraceback(fd, tstate);
|
_Py_DumpTraceback(fd, tstate);
|
||||||
|
@ -272,7 +274,7 @@ faulthandler_dump_traceback_py(PyObject *self,
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (all_threads) {
|
if (all_threads) {
|
||||||
errmsg = _Py_DumpTracebackThreads(fd, tstate->interp, tstate);
|
errmsg = _Py_DumpTracebackThreads(fd, NULL, tstate);
|
||||||
if (errmsg != NULL) {
|
if (errmsg != NULL) {
|
||||||
PyErr_SetString(PyExc_RuntimeError, errmsg);
|
PyErr_SetString(PyExc_RuntimeError, errmsg);
|
||||||
return NULL;
|
return NULL;
|
||||||
|
@ -469,7 +471,6 @@ faulthandler_thread(void *unused)
|
||||||
{
|
{
|
||||||
PyLockStatus st;
|
PyLockStatus st;
|
||||||
const char* errmsg;
|
const char* errmsg;
|
||||||
PyThreadState *current;
|
|
||||||
int ok;
|
int ok;
|
||||||
#if defined(HAVE_PTHREAD_SIGMASK) && !defined(HAVE_BROKEN_PTHREAD_SIGMASK)
|
#if defined(HAVE_PTHREAD_SIGMASK) && !defined(HAVE_BROKEN_PTHREAD_SIGMASK)
|
||||||
sigset_t set;
|
sigset_t set;
|
||||||
|
@ -489,12 +490,9 @@ faulthandler_thread(void *unused)
|
||||||
/* Timeout => dump traceback */
|
/* Timeout => dump traceback */
|
||||||
assert(st == PY_LOCK_FAILURE);
|
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);
|
_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);
|
ok = (errmsg == NULL);
|
||||||
|
|
||||||
if (thread.exit)
|
if (thread.exit)
|
||||||
|
@ -894,7 +892,7 @@ static PyObject *
|
||||||
faulthandler_sigsegv(PyObject *self, PyObject *args)
|
faulthandler_sigsegv(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
int release_gil = 0;
|
int release_gil = 0;
|
||||||
if (!PyArg_ParseTuple(args, "|i:_read_null", &release_gil))
|
if (!PyArg_ParseTuple(args, "|i:_sigsegv", &release_gil))
|
||||||
return NULL;
|
return NULL;
|
||||||
|
|
||||||
if (release_gil) {
|
if (release_gil) {
|
||||||
|
@ -907,6 +905,49 @@ faulthandler_sigsegv(PyObject *self, PyObject *args)
|
||||||
Py_RETURN_NONE;
|
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 *
|
static PyObject *
|
||||||
faulthandler_sigfpe(PyObject *self, PyObject *args)
|
faulthandler_sigfpe(PyObject *self, PyObject *args)
|
||||||
{
|
{
|
||||||
|
@ -1065,6 +1106,11 @@ static PyMethodDef module_methods[] = {
|
||||||
"a SIGSEGV or SIGBUS signal depending on the platform")},
|
"a SIGSEGV or SIGBUS signal depending on the platform")},
|
||||||
{"_sigsegv", faulthandler_sigsegv, METH_VARARGS,
|
{"_sigsegv", faulthandler_sigsegv, METH_VARARGS,
|
||||||
PyDoc_STR("_sigsegv(release_gil=False): raise a SIGSEGV signal")},
|
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,
|
{"_sigabrt", faulthandler_sigabrt, METH_NOARGS,
|
||||||
PyDoc_STR("_sigabrt(): raise a SIGABRT signal")},
|
PyDoc_STR("_sigabrt(): raise a SIGABRT signal")},
|
||||||
{"_sigfpe", (PyCFunction)faulthandler_sigfpe, METH_NOARGS,
|
{"_sigfpe", (PyCFunction)faulthandler_sigfpe, METH_NOARGS,
|
||||||
|
|
|
@ -1275,25 +1275,11 @@ initstdio(void)
|
||||||
static void
|
static void
|
||||||
_Py_FatalError_DumpTracebacks(int fd)
|
_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);
|
fputc('\n', stderr);
|
||||||
fflush(stderr);
|
fflush(stderr);
|
||||||
|
|
||||||
/* display the current Python stack */
|
/* 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,
|
/* 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);
|
_PyGILState_NoteThreadState(t);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyInterpreterState *
|
||||||
|
_PyGILState_GetInterpreterStateUnsafe(void)
|
||||||
|
{
|
||||||
|
return autoInterpreterState;
|
||||||
|
}
|
||||||
|
|
||||||
void
|
void
|
||||||
_PyGILState_Fini(void)
|
_PyGILState_Fini(void)
|
||||||
{
|
{
|
||||||
|
|
|
@ -707,11 +707,56 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current)
|
||||||
handlers if signals were received. */
|
handlers if signals were received. */
|
||||||
const char*
|
const char*
|
||||||
_Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
|
_Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
|
||||||
PyThreadState *current_thread)
|
PyThreadState *current_tstate)
|
||||||
{
|
{
|
||||||
PyThreadState *tstate;
|
PyThreadState *tstate;
|
||||||
unsigned int nthreads;
|
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 */
|
/* Get the current interpreter from the current thread */
|
||||||
tstate = PyInterpreterState_ThreadHead(interp);
|
tstate = PyInterpreterState_ThreadHead(interp);
|
||||||
if (tstate == NULL)
|
if (tstate == NULL)
|
||||||
|
@ -729,7 +774,7 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
|
||||||
PUTS(fd, "...\n");
|
PUTS(fd, "...\n");
|
||||||
break;
|
break;
|
||||||
}
|
}
|
||||||
write_thread_id(fd, tstate, tstate == current_thread);
|
write_thread_id(fd, tstate, tstate == current_tstate);
|
||||||
dump_traceback(fd, tstate, 0);
|
dump_traceback(fd, tstate, 0);
|
||||||
tstate = PyThreadState_Next(tstate);
|
tstate = PyThreadState_Next(tstate);
|
||||||
nthreads++;
|
nthreads++;
|
||||||
|
|
Loading…
Reference in New Issue