| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
_Py_AsyncioDebug (the AsyncioDebug section) describes asyncio task state to tools outside the _asyncio module. In CPython it is read by _remote_debugging, which backs Tachyon and python -m asyncio ps/pstree. Third-party profilers are starting to use it too, for example the in-process sampler in DataDog/dd-trace-py#19802. Two things make it harder to use safely than _Py_DebugOffsets.
1. It can only be found by searching binaries. _Py_DebugOffsets sits at the start of _PyRuntime, which is an exported symbol. Nothing there points to the asyncio table, so readers have to find the AsyncioDebug section themselves: pick loaded files by name, then parse their section headers. This is fragile:
2. The table can't be identified. _Py_DebugOffsets starts with a cookie and the Python version, so a reader can check that it found the right table for the running interpreter. Py_AsyncioModuleDebugOffsets has neither, and no size. A reader that finds an AsyncioDebug section can't confirm that it is an asyncio table, or associate it with the selected runtime. The validation added in gh-148178 checks sizes, bounds and alignment, including local buffer limits, but does not identify the table or associate it with that runtime. A wrong table with plausible values can still pass. _remote_debugging shows the gap: it checks the cookie of every PyRuntime candidate, but takes the first AsyncioDebug section it finds.
Add a pointer to _PyRuntimeState and publish its offset:
struct pyruntimestate {
_Py_DebugOffsets debug_offsets;
/* Address of the _asyncio debug offsets table, or NULL until the
module is initialized. */
void *asyncio_debug_offsets;
...
};
struct _runtime_state {
uint64_t size;
uint64_t finalizing;
uint64_t interpreters_head;
uint64_t asyncio_debug_offsets; /* new */
} runtime_state;_asyncio's module_init() stores &_Py_AsyncioDebug there with a release store. Every interpreter stores the same address. CPython does not unload the extension library, so the table remains valid across interpreter teardown within the initialized runtime. Runtime reinitialization resets the published field to NULL; importing _asyncio publishes it again.
Start Py_AsyncioModuleDebugOffsets with a header:
typedef struct _Py_AsyncioModuleDebugOffsets {
char cookie[8]; /* "xasyncpy" */
uint64_t version; /* PY_VERSION_HEX */
uint64_t size; /* sizeof(Py_AsyncioModuleDebugOffsets) */
struct _asyncio_task_object { ... } asyncio_task_object;
...
} Py_AsyncioModuleDebugOffsets;In _remote_debugging, follow the pointer instead of searching for the section. Reject the table if the cookie doesn't match, if version differs from _Py_DebugOffsets.version, or if size is smaller than the local struct. Add the new field to the runtime-state validation list and update the static_asserts added by Harden validation of remote debugging offset tables #148178.
Keep the AsyncioDebug section as an alternative discovery mechanism. Both table layouts change in 3.16, so readers must be updated for that version; this proposal is not intended for backporting.
Following the pointer associates the table with the selected runtime. The cookie, Python version and size checks detect some invalid or incompatible tables, but do not establish exact build identity: different builds of the same release share PY_VERSION_HEX.
Once a reader has located _PyRuntime, the discovery sequence is, in pseudocode:
off = read_memory(runtime, debug_offsets_layout)
validate_runtime_offsets(off) # cookie, version compatibility and bounds
pointer_address = runtime + off.runtime_state.asyncio_debug_offsets
table_address = read_pointer(pointer_address)
if table_address == NULL:
return unavailable # _asyncio not initialized yet; try again later
header = read_memory(table_address, asyncio_header_layout)
validate_asyncio_header(header, off.version) # cookie, version and size
# Read the supported table fields and validate their sizes, bounds and alignment.
In-process readers should load the pointer with acquire ordering. This works for both a static _asyncio (the POSIX default since 3.15) and a shared one (Windows _asyncio.pyd).
When the table was added in gh-91048 (GH-124640), _asyncio was a shared extension module by default and asyncio-specific state was kept out of the core on purpose, so a section in _asyncio was the natural place for it. Since then:
The change adds one pointer to the runtime, which the core never reads, and the table itself stays in _asyncio.
I have an implementation on this branch, with a ctypes test that follows the pointer from _PyRuntime. The existing test_external_inspection asyncio tests pass with it on Linux. I'm happy to open a PR.
This is a minor feature, which does not need previous discussion elsewhere
| Back | FazBrowse Home | New Git URL |
Activity
pythongh-160107: Make AsyncioDebug reachable from _PyRuntime and add …