Bug description
A native thread can retain a Python thread state while waiting in C code, with no Python functions executing. Its frame chain contains only the base_frame sentinel.
The sampler skips this sentinel, then raises:
RuntimeError: Failed to parse initial frame in chain
This fails the entire sampling call, so valid Python stacks from other threads are also lost.
Reproduction
Create a pthread that registers and detaches a Python thread state:
PyGILState_STATE state = PyGILState_Ensure();
PyThreadState *saved = PyEval_SaveThread();
/* Wait on a pthread condition variable while the parent samples. */
/* Cleanup after sampling. */
PyEval_RestoreThread(saved);
PyGILState_Release(state);
Keep the main thread inside a Python function and sample the child process from its parent:
unwinder = _remote_debugging.RemoteUnwinder(
pid, all_threads=True, cache_frames=False,
)
unwinder.get_stack_trace()
- Without registering the native thread's Python state: 40/40 samples succeed.
- With the retained state: 40/40 samples fail.
- Reproduces with frame caching both enabled and disabled.
Expected behavior
Accept the native thread's empty Python stack and return valid stacks from the other threads.
Suspected cause
The check frame == NULL && PyList_GET_SIZE(ctx->frame_info) == 0 treats the valid empty-stack sentinel as a broken frame chain.
Environment
Linux, unmodified CPython 3.15.0rc2+dev free-threaded build.
Current main has not been tested.
tachyon-empty-native-repro.zip
Linked PRs
Bug description
A native thread can retain a Python thread state while waiting in C code, with no Python functions executing. Its frame chain contains only the
base_framesentinel.The sampler skips this sentinel, then raises:
This fails the entire sampling call, so valid Python stacks from other threads are also lost.
Reproduction
Create a pthread that registers and detaches a Python thread state:
Keep the main thread inside a Python function and sample the child process from its parent:
Expected behavior
Accept the native thread's empty Python stack and return valid stacks from the other threads.
Suspected cause
The check
frame == NULL && PyList_GET_SIZE(ctx->frame_info) == 0treats the valid empty-stack sentinel as a broken frame chain.Environment
Linux, unmodified CPython 3.15.0rc2+dev free-threaded build.
Current main has not been tested.
tachyon-empty-native-repro.zip
Linked PRs