fix(profiling): discover Python 3.14 asyncio offsets by section - #19802
fix(profiling): discover Python 3.14 asyncio offsets by section#19802taegyunkim wants to merge 9 commits into
Conversation
Codeowners resolved asResolved from the full PR diff against |
Circular import analysis
|
Dependency direction analysis
|
🎉 All green!🧪 All tests passed 🔗 Commit SHA: 80c19b7 | Docs | View more details | Give us feedback! |
|
@codex review |
|
Codex Review: Didn't find any major issues. More of your lovely PRs please. Reviewed commit: ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
There was a problem hiding this comment.
Pull request overview
Replaces Python 3.14 compile-time asyncio list offsets with validated runtime discovery, preventing missing or inflated task profiles across patch releases.
Changes:
- Discovers and validates
.AsyncioDebugmetadata on Linux and macOS. - Publishes offsets atomically and fails closed when unavailable.
- Adds validation tests, build integration, and a release note.
Reviewed changes
Copilot reviewed 8 out of 8 changed files in this pull request and generated no comments.
Show a summary per file
| File | Description |
|---|---|
releasenotes/notes/fix-py314-asyncio-task-offsets-b08edd4c76321a26.yaml |
Documents the customer-visible fix. |
ddtrace/internal/datadog/profiling/stack/test/test_sampling_cycle_state.cpp |
Tests offset validation and storage. |
ddtrace/internal/datadog/profiling/stack/src/stack.cpp |
Initializes runtime offsets. |
ddtrace/internal/datadog/profiling/stack/src/echion/threads.cc |
Uses discovered offsets with safety checks. |
ddtrace/internal/datadog/profiling/stack/src/echion/asyncio_debug.cc |
Implements ELF and Mach-O discovery. |
ddtrace/internal/datadog/profiling/stack/echion/echion/echion_sampler.h |
Stores offsets atomically. |
ddtrace/internal/datadog/profiling/stack/echion/echion/cpython/asyncio_debug.h |
Defines and validates the runtime table. |
ddtrace/internal/datadog/profiling/stack/CMakeLists.txt |
Builds and links the discovery implementation. |
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
Description
Python 3.14 exposes the private asyncio task-list layouts through the runtime
.AsyncioDebugtable, but Echion currently compiles the two list-head offsets into_stack.sowithoffsetof(). A normalcp314wheel can then use the wrong offsets on a different Python 3.14 patch release. For example, the ddtrace 4.13.1 wheel was built with CPython 3.14.4 offsets (0x370for the thread head and0x2a68for the interpreter head), while CPython 3.14.2 uses0x368and0x2a60. The mismatch can drop task attribution or traverse malformed memory and severely inflate task profiles.CPython emits
.AsyncioDebugas runtime metadata for remote unwinders. The fixed 104-byte table describes the relevant task, interpreter, and thread layouts for the running interpreter, so reading it at initialization avoids assuming that private CPython structures remain stable across patch releases. This change validates the complete 13-field table and publishes the two list-head offsets Echion needs to the lock-free sampler.An alternative was to get the
_asynciomodule state and read its finaldebug_offsetspointer. That works with the CPython 3.14 versions inspected becausedebug_offsetsis currently the last member of the privateasyncio_statestructure. However, its position and the structure's field ordering are not an ABI contract. Assuming it remains last would replace one patch-sensitive private-layout dependency with another. Discovering the named section follows CPython's remote-unwinding mechanism and does not depend on the_asynciomodule-state layout.Linux discovery enumerates loaded images, maps each candidate ELF file, finds
.AsyncioDebug, and verifies that the section belongs to a loadedPT_LOADsegment. macOS discovery scans loaded Mach-O commands and locatesAsyncioDebugin__DATA. Invalid candidates are skipped, and unavailable or invalid metadata fails closed. Discovery is cached after the first initialization attempt because asyncio is already loaded and its layout cannot change during the process lifetime.Scope is intentionally limited to:
_PyThreadStateImpl.asyncio_tasks_headPyInterpreterState.asyncio_tasks_headTaskObj.task_noderemains unchanged. Pure-Python tasks remain covered by_scheduled_tasks. The separate linked-list consistency checks in #19798 are not included here.The deterministic cross-patch reproducer and explicit-offset audit are in DataDog/experimental#13678.
Testing
CI passing.
Risks