Skip to content

Commit 2c982a2

Browse files
committed
gh-157605: Accept empty native-thread stacks when sampling
1 parent 6b5d414 commit 2c982a2

3 files changed

Lines changed: 35 additions & 5 deletions

File tree

‎Lib/test/test_external_inspection.py‎

Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1666,6 +1666,39 @@ def test_self_trace(self):
16661666
self.assertEqual(this_thread_stack[1].funcname, "TestGetStackTrace.test_self_trace")
16671667
self.assertTrue(this_thread_stack[1].filename.endswith("test_external_inspection.py"))
16681668

1669+
@skip_if_not_supported
1670+
@unittest.skipIf(
1671+
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
1672+
"Test only runs on Linux with process_vm_readv support",
1673+
)
1674+
def test_empty_native_thread_stack(self):
1675+
_testcapi = import_module("_testcapi")
1676+
lock = threading.Lock()
1677+
lock.acquire()
1678+
# A built-in callback leaves the C thread's Python stack empty.
1679+
_testcapi.call_in_temporary_c_thread(lock.acquire, False)
1680+
try:
1681+
for cache_frames in (False, True):
1682+
with self.subTest(cache_frames=cache_frames):
1683+
unwinder = RemoteUnwinder(
1684+
os.getpid(), all_threads=True, cache_frames=cache_frames,
1685+
)
1686+
_get_stack_trace_with_retry(
1687+
unwinder, condition=lambda trace: len(trace[0].threads) == 2,
1688+
)
1689+
threads = unwinder.get_stack_trace()[0].threads
1690+
native_stack, python_stack = sorted(
1691+
(thread.frame_info for thread in threads), key=len,
1692+
)
1693+
self.assertEqual(native_stack, [])
1694+
self.assertEqual(
1695+
python_stack[0].funcname,
1696+
"TestGetStackTrace.test_empty_native_thread_stack",
1697+
)
1698+
finally:
1699+
lock.release()
1700+
_testcapi.join_temporary_c_thread()
1701+
16691702
@skip_if_not_supported
16701703
@unittest.skipIf(
16711704
sys.platform == "linux" and not PROCESS_VM_READV_SUPPORTED,
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :mod:`profiling.sampling` failing when a native thread has an empty Python
2+
stack.

‎Modules/_remote_debugging/frames.c‎

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -358,11 +358,6 @@ process_frame_chain(
358358
continue;
359359
}
360360

361-
if (frame == NULL && PyList_GET_SIZE(ctx->frame_info) == 0) {
362-
const char *e = "Failed to parse initial frame in chain";
363-
PyErr_SetString(PyExc_RuntimeError, e);
364-
return -1;
365-
}
366361
PyObject *extra_frame = NULL;
367362
if (unwinder->gc && frame_addr == ctx->gc_frame) {
368363
_Py_DECLARE_STR(gc, "<GC>");

0 commit comments

Comments
 (0)