Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
22 changes: 22 additions & 0 deletions Lib/test/test_faulthandler.py
Original file line number Diff line number Diff line change
Expand Up @@ -707,6 +707,28 @@ def test_dump_traceback_later(self):
def test_dump_traceback_later_repeat(self):
self.check_dump_traceback_later(repeat=True)

@threading_helper.requires_working_threading()
@support.requires_gil_enabled()
def test_dump_traceback_later_does_not_wait_for_gil(self):
code = dedent("""
import faulthandler
import sys

sys.setswitchinterval(3600.0)
faulthandler.dump_traceback_later(0.05, exit=True)
while True:
pass
""")
with support.SuppressCrashReport():
process = subprocess.run(
[sys.executable, '-I', '-c', code],
stdout=subprocess.PIPE,
stderr=subprocess.PIPE,
timeout=support.SHORT_TIMEOUT,
)
self.assertEqual(process.returncode, 1)
self.assertIn(b'Timeout', process.stderr)

def test_dump_traceback_later_cancel(self):
self.check_dump_traceback_later(cancel=True)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Improve :func:`faulthandler.dump_traceback_later` when another thread
concurrently frees code metadata. The traceback walker now detects common
freed-memory states instead of dereferencing them.
45 changes: 41 additions & 4 deletions Objects/codeobject.c
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
#include "pycore_interpframe.h" // FRAME_SPECIALS_SIZE
#include "pycore_opcode_metadata.h" // _PyOpcode_Caches
#include "pycore_opcode_utils.h" // RESUME_AT_FUNC_START
#include "pycore_object.h" // _PyObject_IsFreed()
#include "pycore_optimizer.h" // _Py_ExecutorDetach
#include "pycore_pymem.h" // _PyMem_FreeDelayed()
#include "pycore_pystate.h" // _PyInterpreterState_GET()
Expand Down Expand Up @@ -1035,17 +1036,53 @@ PyCode_Addr2Line(PyCodeObject *co, int addrq)
int
_PyCode_SafeAddr2Line(PyCodeObject *co, int addrq)
{
if (_PyObject_IsFreed((PyObject *)co) || !PyCode_Check(co)) {
return -1;
}
if (addrq < 0) {
return co->co_firstlineno;
}
if (co->_co_monitoring && co->_co_monitoring->lines) {
return _Py_Instrumentation_GetLine(co, co->_co_monitoring->lines, addrq/sizeof(_Py_CODEUNIT));

Py_ssize_t code_size = Py_SIZE(co);
if (code_size < 0 || addrq / (int)sizeof(_Py_CODEUNIT) >= code_size) {
return -1;
}

_PyCoMonitoringData *monitoring =
_Py_atomic_load_ptr_acquire(&co->_co_monitoring);
if (monitoring != NULL) {
if (_PyMem_IsPtrFreed(monitoring)) {
return -1;
}
_PyCoLineInstrumentationData *lines =
_Py_atomic_load_ptr_acquire(&monitoring->lines);
if (lines != NULL) {
if (_PyMem_IsPtrFreed(lines)
|| _PyObject_IsFreed((PyObject *)co)
|| !PyCode_Check(co))
{
return -1;
}
return _Py_Instrumentation_GetLine(
co, lines, addrq / sizeof(_Py_CODEUNIT));
}
}

if (_PyObject_IsFreed((PyObject *)co) || !PyCode_Check(co)) {
return -1;
}
if (!(addrq >= 0 && addrq < _PyCode_NBYTES(co))) {
PyObject *linetable = co->co_linetable;
if (_PyObject_IsFreed(linetable) || !PyBytes_Check(linetable)) {
return -1;
}
int firstlineno = co->co_firstlineno;

PyCodeAddressRange bounds;
_PyCode_InitAddressRange(co, &bounds);
_PyLineTable_InitAddressRange(
PyBytes_AS_STRING(linetable),
PyBytes_GET_SIZE(linetable),
firstlineno,
&bounds);
return _PyCode_CheckLineNumber(addrq, &bounds);
}

Expand Down
39 changes: 27 additions & 12 deletions Python/traceback.c
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
#include "pycore_frame.h" // PyFrameObject
#include "pycore_interp.h" // PyInterpreterState.gc
#include "pycore_interpframe.h" // _PyFrame_GetCode()
#include "pycore_object.h" // _PyObject_IsFreed()
#include "pycore_pyerrors.h" // _PyErr_GetRaisedException()
#include "pycore_pystate.h" // _PyThreadState_GET()
#include "pycore_traceback.h" // EXCEPTION_TB_HEADER
Expand Down Expand Up @@ -898,15 +899,17 @@ dump_char(int fd, char ch)
void
_Py_DumpASCII(int fd, PyObject *text)
{
PyASCIIObject *ascii = _PyASCIIObject_CAST(text);
Py_ssize_t i, size;
int truncated;
int kind;
void *data = NULL;
Py_UCS4 ch;

if (!PyUnicode_Check(text))
if (_PyObject_IsFreed(text) || !PyUnicode_Check(text)) {
return;
}

PyASCIIObject *ascii = _PyASCIIObject_CAST(text);

size = ascii->length;
kind = ascii->state.kind;
Expand Down Expand Up @@ -1043,19 +1046,26 @@ dump_frame(int fd, _PyInterpreterFrame *frame)

int res = 0;
PUTS(fd, " File ");
if (code->co_filename != NULL
&& PyUnicode_Check(code->co_filename))
{
PUTS(fd, "\"");
_Py_DumpASCII(fd, code->co_filename);
PUTS(fd, "\"");
if (_PyObject_IsFreed((PyObject *)code) || !PyCode_Check(code)) {
PUTS(fd, "???, line ??? in ???\n");
return -1;
}
else {
PyObject *filename = code->co_filename;
if (_PyObject_IsFreed(filename) || !PyUnicode_Check(filename)) {
PUTS(fd, "???");
res = -1;
}
else {
PUTS(fd, "\"");
_Py_DumpASCII(fd, filename);
PUTS(fd, "\"");
}

PUTS(fd, ", line ");
if (_PyObject_IsFreed((PyObject *)code) || !PyCode_Check(code)) {
PUTS(fd, "??? in ???\n");
return -1;
}
int lasti = _PyFrame_SafeGetLasti(frame);
int lineno = -1;
if (lasti >= 0) {
Expand All @@ -1070,13 +1080,18 @@ dump_frame(int fd, _PyInterpreterFrame *frame)
}

PUTS(fd, " in ");
if (code->co_name != NULL && PyUnicode_Check(code->co_name)) {
_Py_DumpASCII(fd, code->co_name);
if (_PyObject_IsFreed((PyObject *)code) || !PyCode_Check(code)) {
PUTS(fd, "???\n");
return -1;
}
else {
PyObject *name = code->co_name;
if (_PyObject_IsFreed(name) || !PyUnicode_Check(name)) {
PUTS(fd, "???");
res = -1;
}
else {
_Py_DumpASCII(fd, name);
}
PUTS(fd, "\n");
return res;
}
Expand Down
Loading