Skip to content

profiling.sampling fails when a native thread has an empty Python stack #157605

Description

@liuzhijie-0614

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

Activity

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Labels

    Projects

    No projects

      Milestone

      No milestone

      Relationships

      None yet

      Development

      No branches or pull requests

      Issue actions