Thanks to visit codestin.com
Credit goes to github.com

Skip to content

Commit 861d9ab

Browse files
committed
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
1 parent c36674a commit 861d9ab

7 files changed

Lines changed: 164 additions & 48 deletions

File tree

Include/pystate.h

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -243,15 +243,23 @@ PyAPI_FUNC(void) PyGILState_Release(PyGILState_STATE);
243243
*/
244244
PyAPI_FUNC(PyThreadState *) PyGILState_GetThisThreadState(void);
245245

246-
/* Helper/diagnostic function - return 1 if the current thread
247-
* currently holds the GIL, 0 otherwise
248-
*/
249246
#ifndef Py_LIMITED_API
250247
/* Issue #26558: Flag to disable PyGILState_Check().
251-
If set, PyGILState_Check() always return 1. */
248+
If set to non-zero, PyGILState_Check() always return 1. */
252249
PyAPI_DATA(int) _PyGILState_check_enabled;
253250

251+
/* Helper/diagnostic function - return 1 if the current thread
252+
currently holds the GIL, 0 otherwise.
253+
254+
The function returns 1 if _PyGILState_check_enabled is non-zero. */
254255
PyAPI_FUNC(int) PyGILState_Check(void);
256+
257+
/* Unsafe function to get the single PyInterpreterState used by this process'
258+
GILState implementation.
259+
260+
Return NULL before _PyGILState_Init() is called and after _PyGILState_Fini()
261+
is called. */
262+
PyAPI_FUNC(PyInterpreterState *) _PyGILState_GetInterpreterStateUnsafe(void);
255263
#endif
256264

257265
#endif /* #ifdef WITH_THREAD */

Include/traceback.h

Lines changed: 17 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,19 +53,32 @@ PyAPI_DATA(void) _Py_DumpTraceback(
5353
PyThreadState *tstate);
5454

5555
/* Write the traceback of all threads into the file 'fd'. current_thread can be
56-
NULL. Return NULL on success, or an error message on error.
56+
NULL.
57+
58+
Return NULL on success, or an error message on error.
5759
5860
This function is written for debug purpose only. It calls
5961
_Py_DumpTraceback() for each thread, and so has the same limitations. It
6062
only write the traceback of the first 100 threads: write "..." if there are
6163
more threads.
6264
65+
If current_tstate is NULL, the function tries to get the Python thread state
66+
of the current thread. It is not an error if the function is unable to get
67+
the current Python thread state.
68+
69+
If interp is NULL, the function tries to get the interpreter state from
70+
the current Python thread state, or from
71+
_PyGILState_GetInterpreterStateUnsafe() in last resort.
72+
73+
It is better to pass NULL to interp and current_tstate, the function tries
74+
different options to retrieve these informations.
75+
6376
This function is signal safe. */
6477

6578
PyAPI_DATA(const char*) _Py_DumpTracebackThreads(
66-
int fd, PyInterpreterState *interp,
67-
PyThreadState *current_thread);
68-
79+
int fd,
80+
PyInterpreterState *interp,
81+
PyThreadState *current_tstate);
6982

7083
#ifndef Py_LIMITED_API
7184

Lib/test/test_faulthandler.py

Lines changed: 24 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -58,8 +58,9 @@ def get_output(self, code, filename=None, fd=None):
5858
pass_fds.append(fd)
5959
with support.SuppressCrashReport():
6060
process = script_helper.spawn_python('-c', code, pass_fds=pass_fds)
61-
stdout, stderr = process.communicate()
62-
exitcode = process.wait()
61+
with process:
62+
stdout, stderr = process.communicate()
63+
exitcode = process.wait()
6364
output = support.strip_python_stderr(stdout)
6465
output = output.decode('ascii', 'backslashreplace')
6566
if filename:
@@ -73,34 +74,34 @@ def get_output(self, code, filename=None, fd=None):
7374
with open(fd, "rb", closefd=False) as fp:
7475
output = fp.read()
7576
output = output.decode('ascii', 'backslashreplace')
76-
output = re.sub('Current thread 0x[0-9a-f]+',
77-
'Current thread XXX',
78-
output)
7977
return output.splitlines(), exitcode
8078

8179
def check_fatal_error(self, code, line_number, name_regex,
8280
filename=None, all_threads=True, other_regex=None,
83-
fd=None):
81+
fd=None, know_current_thread=True):
8482
"""
8583
Check that the fault handler for fatal errors is enabled and check the
8684
traceback from the child process output.
8785
8886
Raise an error if the output doesn't match the expected format.
8987
"""
9088
if all_threads:
91-
header = 'Current thread XXX (most recent call first)'
89+
if know_current_thread:
90+
header = 'Current thread 0x[0-9a-f]+'
91+
else:
92+
header = 'Thread 0x[0-9a-f]+'
9293
else:
93-
header = 'Stack (most recent call first)'
94+
header = 'Stack'
9495
regex = """
9596
^Fatal Python error: {name}
9697
97-
{header}:
98+
{header} \(most recent call first\):
9899
File "<string>", line {lineno} in <module>
99100
"""
100101
regex = dedent(regex.format(
101102
lineno=line_number,
102103
name=name_regex,
103-
header=re.escape(header))).strip()
104+
header=header)).strip()
104105
if other_regex:
105106
regex += '|' + other_regex
106107
output, exitcode = self.get_output(code, filename=filename, fd=fd)
@@ -129,6 +130,17 @@ def test_sigsegv(self):
129130
3,
130131
'Segmentation fault')
131132

133+
@unittest.skipIf(not HAVE_THREADS, 'need threads')
134+
def test_fatal_error_c_thread(self):
135+
self.check_fatal_error("""
136+
import faulthandler
137+
faulthandler.enable()
138+
faulthandler._fatal_error_c_thread()
139+
""",
140+
3,
141+
'in new thread',
142+
know_current_thread=False)
143+
132144
def test_sigabrt(self):
133145
self.check_fatal_error("""
134146
import faulthandler
@@ -465,7 +477,7 @@ def run(self):
465477
File ".*threading.py", line [0-9]+ in _bootstrap_inner
466478
File ".*threading.py", line [0-9]+ in _bootstrap
467479
468-
Current thread XXX \(most recent call first\):
480+
Current thread 0x[0-9a-f]+ \(most recent call first\):
469481
File "<string>", line {lineno} in dump
470482
File "<string>", line 28 in <module>$
471483
"""
@@ -637,7 +649,7 @@ def handler(signum, frame):
637649
trace = '\n'.join(trace)
638650
if not unregister:
639651
if all_threads:
640-
regex = 'Current thread XXX \(most recent call first\):\n'
652+
regex = 'Current thread 0x[0-9a-f]+ \(most recent call first\):\n'
641653
else:
642654
regex = 'Stack \(most recent call first\):\n'
643655
regex = expected_traceback(14, 32, regex)

Modules/faulthandler.c

Lines changed: 57 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -202,8 +202,9 @@ faulthandler_get_fileno(PyObject **file_ptr)
202202
static PyThreadState*
203203
get_thread_state(void)
204204
{
205-
PyThreadState *tstate = PyThreadState_Get();
205+
PyThreadState *tstate = _PyThreadState_UncheckedGet();
206206
if (tstate == NULL) {
207+
/* just in case but very unlikely... */
207208
PyErr_SetString(PyExc_RuntimeError,
208209
"unable to get the current thread state");
209210
return NULL;
@@ -234,11 +235,12 @@ faulthandler_dump_traceback(int fd, int all_threads,
234235
PyGILState_GetThisThreadState(). */
235236
tstate = PyGILState_GetThisThreadState();
236237
#else
237-
tstate = PyThreadState_Get();
238+
tstate = _PyThreadState_UncheckedGet();
238239
#endif
239240

240-
if (all_threads)
241-
_Py_DumpTracebackThreads(fd, interp, tstate);
241+
if (all_threads) {
242+
(void)_Py_DumpTracebackThreads(fd, NULL, tstate);
243+
}
242244
else {
243245
if (tstate != NULL)
244246
_Py_DumpTraceback(fd, tstate);
@@ -272,7 +274,7 @@ faulthandler_dump_traceback_py(PyObject *self,
272274
return NULL;
273275

274276
if (all_threads) {
275-
errmsg = _Py_DumpTracebackThreads(fd, tstate->interp, tstate);
277+
errmsg = _Py_DumpTracebackThreads(fd, NULL, tstate);
276278
if (errmsg != NULL) {
277279
PyErr_SetString(PyExc_RuntimeError, errmsg);
278280
return NULL;
@@ -469,7 +471,6 @@ faulthandler_thread(void *unused)
469471
{
470472
PyLockStatus st;
471473
const char* errmsg;
472-
PyThreadState *current;
473474
int ok;
474475
#if defined(HAVE_PTHREAD_SIGMASK) && !defined(HAVE_BROKEN_PTHREAD_SIGMASK)
475476
sigset_t set;
@@ -489,12 +490,9 @@ faulthandler_thread(void *unused)
489490
/* Timeout => dump traceback */
490491
assert(st == PY_LOCK_FAILURE);
491492

492-
/* get the thread holding the GIL, NULL if no thread hold the GIL */
493-
current = _PyThreadState_UncheckedGet();
494-
495493
_Py_write_noraise(thread.fd, thread.header, (int)thread.header_len);
496494

497-
errmsg = _Py_DumpTracebackThreads(thread.fd, thread.interp, current);
495+
errmsg = _Py_DumpTracebackThreads(thread.fd, thread.interp, NULL);
498496
ok = (errmsg == NULL);
499497

500498
if (thread.exit)
@@ -894,7 +892,7 @@ static PyObject *
894892
faulthandler_sigsegv(PyObject *self, PyObject *args)
895893
{
896894
int release_gil = 0;
897-
if (!PyArg_ParseTuple(args, "|i:_read_null", &release_gil))
895+
if (!PyArg_ParseTuple(args, "|i:_sigsegv", &release_gil))
898896
return NULL;
899897

900898
if (release_gil) {
@@ -907,6 +905,49 @@ faulthandler_sigsegv(PyObject *self, PyObject *args)
907905
Py_RETURN_NONE;
908906
}
909907

908+
#ifdef WITH_THREAD
909+
static void
910+
faulthandler_fatal_error_thread(void *plock)
911+
{
912+
PyThread_type_lock *lock = (PyThread_type_lock *)plock;
913+
914+
Py_FatalError("in new thread");
915+
916+
/* notify the caller that we are done */
917+
PyThread_release_lock(lock);
918+
}
919+
920+
static PyObject *
921+
faulthandler_fatal_error_c_thread(PyObject *self, PyObject *args)
922+
{
923+
long thread;
924+
PyThread_type_lock lock;
925+
926+
faulthandler_suppress_crash_report();
927+
928+
lock = PyThread_allocate_lock();
929+
if (lock == NULL)
930+
return PyErr_NoMemory();
931+
932+
PyThread_acquire_lock(lock, WAIT_LOCK);
933+
934+
thread = PyThread_start_new_thread(faulthandler_fatal_error_thread, lock);
935+
if (thread == -1) {
936+
PyThread_free_lock(lock);
937+
PyErr_SetString(PyExc_RuntimeError, "unable to start the thread");
938+
return NULL;
939+
}
940+
941+
/* wait until the thread completes: it will never occur, since Py_FatalError()
942+
exits the process immedialty. */
943+
PyThread_acquire_lock(lock, WAIT_LOCK);
944+
PyThread_release_lock(lock);
945+
PyThread_free_lock(lock);
946+
947+
Py_RETURN_NONE;
948+
}
949+
#endif
950+
910951
static PyObject *
911952
faulthandler_sigfpe(PyObject *self, PyObject *args)
912953
{
@@ -1065,6 +1106,11 @@ static PyMethodDef module_methods[] = {
10651106
"a SIGSEGV or SIGBUS signal depending on the platform")},
10661107
{"_sigsegv", faulthandler_sigsegv, METH_VARARGS,
10671108
PyDoc_STR("_sigsegv(release_gil=False): raise a SIGSEGV signal")},
1109+
#ifdef WITH_THREAD
1110+
{"_fatal_error_c_thread", faulthandler_fatal_error_c_thread, METH_NOARGS,
1111+
PyDoc_STR("fatal_error_c_thread(): "
1112+
"call Py_FatalError() in a new C thread.")},
1113+
#endif
10681114
{"_sigabrt", faulthandler_sigabrt, METH_NOARGS,
10691115
PyDoc_STR("_sigabrt(): raise a SIGABRT signal")},
10701116
{"_sigfpe", (PyCFunction)faulthandler_sigfpe, METH_NOARGS,

Python/pylifecycle.c

Lines changed: 1 addition & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1275,25 +1275,11 @@ initstdio(void)
12751275
static void
12761276
_Py_FatalError_DumpTracebacks(int fd)
12771277
{
1278-
PyThreadState *tstate;
1279-
1280-
#ifdef WITH_THREAD
1281-
/* PyGILState_GetThisThreadState() works even if the GIL was released */
1282-
tstate = PyGILState_GetThisThreadState();
1283-
#else
1284-
tstate = PyThreadState_GET();
1285-
#endif
1286-
if (tstate == NULL) {
1287-
/* _Py_DumpTracebackThreads() requires the thread state to display
1288-
* frames */
1289-
return;
1290-
}
1291-
12921278
fputc('\n', stderr);
12931279
fflush(stderr);
12941280

12951281
/* display the current Python stack */
1296-
_Py_DumpTracebackThreads(fd, tstate->interp, tstate);
1282+
_Py_DumpTracebackThreads(fd, NULL, NULL);
12971283
}
12981284

12991285
/* Print the current exception (if an exception is set) with its traceback,

Python/pystate.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -714,6 +714,12 @@ _PyGILState_Init(PyInterpreterState *i, PyThreadState *t)
714714
_PyGILState_NoteThreadState(t);
715715
}
716716

717+
PyInterpreterState *
718+
_PyGILState_GetInterpreterStateUnsafe(void)
719+
{
720+
return autoInterpreterState;
721+
}
722+
717723
void
718724
_PyGILState_Fini(void)
719725
{

Python/traceback.c

Lines changed: 47 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -707,11 +707,56 @@ write_thread_id(int fd, PyThreadState *tstate, int is_current)
707707
handlers if signals were received. */
708708
const char*
709709
_Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
710-
PyThreadState *current_thread)
710+
PyThreadState *current_tstate)
711711
{
712712
PyThreadState *tstate;
713713
unsigned int nthreads;
714714

715+
#ifdef WITH_THREAD
716+
if (current_tstate == NULL) {
717+
/* _Py_DumpTracebackThreads() is called from signal handlers by
718+
faulthandler.
719+
720+
SIGSEGV, SIGFPE, SIGABRT, SIGBUS and SIGILL are synchronous signals
721+
and are thus delivered to the thread that caused the fault. Get the
722+
Python thread state of the current thread.
723+
724+
PyThreadState_Get() doesn't give the state of the thread that caused
725+
the fault if the thread released the GIL, and so this function
726+
cannot be used. Read the thread local storage (TLS) instead: call
727+
PyGILState_GetThisThreadState(). */
728+
current_tstate = PyGILState_GetThisThreadState();
729+
}
730+
731+
if (interp == NULL) {
732+
if (current_tstate == NULL) {
733+
interp = _PyGILState_GetInterpreterStateUnsafe();
734+
if (interp == NULL) {
735+
/* We need the interpreter state to get Python threads */
736+
return "unable to get the interpreter state";
737+
}
738+
}
739+
else {
740+
interp = current_tstate->interp;
741+
}
742+
}
743+
#else
744+
if (current_tstate == NULL) {
745+
/* Call _PyThreadState_UncheckedGet() instead of PyThreadState_Get()
746+
to not fail with a fatal error if the thread state is NULL. */
747+
current_thread = _PyThreadState_UncheckedGet();
748+
}
749+
750+
if (interp == NULL) {
751+
if (current_tstate == NULL) {
752+
/* We need the interpreter state to get Python threads */
753+
return "unable to get the interpreter state";
754+
}
755+
interp = current_tstate->interp;
756+
}
757+
#endif
758+
assert(interp != NULL);
759+
715760
/* Get the current interpreter from the current thread */
716761
tstate = PyInterpreterState_ThreadHead(interp);
717762
if (tstate == NULL)
@@ -729,7 +774,7 @@ _Py_DumpTracebackThreads(int fd, PyInterpreterState *interp,
729774
PUTS(fd, "...\n");
730775
break;
731776
}
732-
write_thread_id(fd, tstate, tstate == current_thread);
777+
write_thread_id(fd, tstate, tstate == current_tstate);
733778
dump_traceback(fd, tstate, 0);
734779
tstate = PyThreadState_Next(tstate);
735780
nthreads++;

0 commit comments

Comments
 (0)