Skip to content

GH-91048: Add utils for capturing async call stack for asyncio programs and enable profiling #124640

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 88 commits into from
Jan 22, 2025
Merged
Show file tree
Hide file tree
Changes from 45 commits
Commits
Show all changes
88 commits
Select commit Hold shift + click to select a range
1b01a91
Add `f_generator` property to Python frame objects
1st1 Sep 24, 2024
0fc5511
Working implementation of `asyncio.capture_call_stack()`
1st1 Sep 25, 2024
1d20a51
Address Guido's comments
1st1 Sep 26, 2024
c8be18e
Add a comment for capture_call_stack()
1st1 Sep 26, 2024
abf2cb9
Add a couple more tests
1st1 Sep 26, 2024
20ceab7
Remove setter for C impl of Task._awaited_by
1st1 Sep 26, 2024
72d9321
Intoduce cr_task
1st1 Sep 26, 2024
c9475f6
Unbreak shield() and gather()
1st1 Sep 26, 2024
e1099e9
Add convinience fields to C Task/Future for profilers
1st1 Sep 26, 2024
817f88b
Fix ups
1st1 Sep 27, 2024
54386ac
Add basic docs
1st1 Sep 27, 2024
98434f0
Implement, test, and document asyncio.print_call_stack()
1st1 Sep 28, 2024
485c166
Reorder a few things
1st1 Sep 28, 2024
8802be7
Add a test to exercise asyncio stack traces in out-of-process profilers
pablogsal Sep 29, 2024
1ddc9cf
Refactor the section-generated code into an evil macro
pablogsal Sep 30, 2024
bc9beb8
Rename "capture_call_stack()" et al to "capture_call_graph()"
1st1 Oct 2, 2024
fd141d4
Add NEWS
1st1 Oct 2, 2024
2d72f24
Per mpage's suggestion: use traceback style formatring for frames
1st1 Oct 2, 2024
391defa
mpage feedback: fix typo
1st1 Oct 2, 2024
d6357fd
Per mpage suggestion: get rid of CoroutineCallGraphEntry
1st1 Oct 2, 2024
bb3b6df
Strip sloppy white space!
1st1 Oct 2, 2024
54c99ec
Fix sloppy set iteration!
1st1 Oct 2, 2024
c1a4f09
Fix a sloppy test!
1st1 Oct 2, 2024
027d522
Run 'make regen-all'
1st1 Oct 2, 2024
c2d5ec6
Add a what's new entry
1st1 Oct 2, 2024
08d09eb
Fix (hopefully) a compiler warning
1st1 Oct 2, 2024
fe3113b
Fix sloppy what's new!
1st1 Oct 2, 2024
18ec26d
Fix CI complaining about suspicious global in C
1st1 Oct 2, 2024
e4cc462
Add a test for depth=2
1st1 Oct 2, 2024
d5cdc36
Add critical sections for free threaded builds
pablogsal Oct 2, 2024
83606f2
Add more slopy tests
pablogsal Oct 2, 2024
5edac41
Apply suggestions from code review
1st1 Oct 16, 2024
8dc6d34
Apply suggestions from code review
1st1 Oct 16, 2024
30884ea
Update Modules/_asynciomodule.c
1st1 Oct 16, 2024
1317658
Update Modules/_asynciomodule.c
1st1 Oct 16, 2024
81b0a31
Use dataclasses instead of tuples for asyncio.stack
1st1 Oct 16, 2024
258ce3d
Fix sloppy whitespace!
1st1 Oct 16, 2024
b9ecefb
Remove critical sections for now
pablogsal Oct 16, 2024
b77dcb0
Eplain the weird macro
1st1 Oct 16, 2024
8867946
Fix test
pablogsal Oct 16, 2024
87d2524
Merge remote-tracking branch 'upstream/main' into stack
pablogsal Oct 16, 2024
b47bef1
Add a docs clarification
1st1 Oct 16, 2024
230b7ec
Fix typing in asyncio.stack
ambv Oct 16, 2024
b1d6158
Fix memory leak
pablogsal Oct 17, 2024
ac51364
Address comments from Kumar's review
ambv Oct 17, 2024
c7e59eb
Fix rare Mach-O linker bug when .bss is uninitialized data
ambv Oct 22, 2024
9eba5e1
Merge branch 'main' into pr-124640
ambv Oct 22, 2024
59121f6
you saw nothing, okay
ambv Oct 22, 2024
f8f48f0
Update Lib/asyncio/futures.py
1st1 Oct 23, 2024
74c5ad1
Remove unnecessary imports from tests
ambv Oct 30, 2024
067c043
Remove cr_task/gi_task
ambv Oct 31, 2024
9f04911
Fix refleaks
ambv Nov 13, 2024
0774805
Rename "asyncio.stack" to "asyncio.graph"
ambv Nov 13, 2024
3048493
Allow returning a string with `format_call_graph`
ambv Nov 13, 2024
1f42873
Add test for eager task factory support
ambv Nov 13, 2024
7799391
Merge branch 'main' into pr-124640
ambv Nov 13, 2024
03ed5c1
Make _asyncio_awaited_by a frozenset in the Python version as well pe…
ambv Nov 13, 2024
21f9ea9
Address picnixz' feedback
1st1 Nov 23, 2024
8a43dfa
Blacken the C/Py test code by hand, by request from picnixz
1st1 Nov 23, 2024
b3fae68
More style fixes per picnixz' suggestions
1st1 Nov 23, 2024
d0aedf0
Address Kumar's latest comment
1st1 Nov 23, 2024
df0032a
diff cleanup
kumaraditya303 Nov 23, 2024
0ce241b
change dataclasses to use tuples
kumaraditya303 Nov 23, 2024
8f126f6
doc fixes
kumaraditya303 Nov 23, 2024
966d84e
remove reduntant headers include and add my name to whatsnew
kumaraditya303 Nov 23, 2024
f56468a
improve comment
kumaraditya303 Nov 23, 2024
404b88a
fix leave task
kumaraditya303 Nov 23, 2024
911fed8
fix external inspection on linux
kumaraditya303 Nov 23, 2024
ab511a4
minor format
kumaraditya303 Nov 23, 2024
c3c685a
try to fix docs build
kumaraditya303 Nov 23, 2024
785adeb
Update Doc/whatsnew/3.14.rst
kumaraditya303 Nov 23, 2024
a577328
Match indentation with _asynciomodule.c after ab511a4f67ffd88f7709c1b…
ambv Nov 23, 2024
064129a
Improve comments after f56468af8b3789d0e20be30715c20ac1bb367641
ambv Nov 23, 2024
ce332d9
Restore the Oxford comma
ambv Nov 23, 2024
d6d943f
Address remaining suggestions of Andrew Svetlov
ambv Nov 23, 2024
703ff46
Fix gather()
1st1 Nov 23, 2024
e867863
Replace lambda by closure
pablogsal Nov 26, 2024
9cb5b29
Let’s not emphasize *asyncio*
1st1 Nov 26, 2024
61b2b7b
Apply suggestions from Irit's review
ambv Nov 26, 2024
9533ab9
Address remaining review by Irit
ambv Nov 26, 2024
596191d
Test pure-Python and C-accelerated versions of future_add_to/future_d…
ambv Nov 26, 2024
ad9152e
fix blooper
ambv Nov 26, 2024
066bf21
Fix another blooper
ambv Nov 26, 2024
4caeec4
Fix crash
pablogsal Jan 21, 2025
38f061d
Merge remote-tracking branch 'upstream/main' into stack
pablogsal Jan 21, 2025
a8dd667
use private method for the policy
pablogsal Jan 22, 2025
cf8f5e5
Avoid importing typing
ambv Jan 22, 2025
eda9c7c
Remove debug printing from test_asyncio.test_graph
ambv Jan 22, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
141 changes: 141 additions & 0 deletions Doc/library/asyncio-stack.rst
Original file line number Diff line number Diff line change
@@ -0,0 +1,141 @@
.. currentmodule:: asyncio


.. _asyncio-stack:

===================
Stack Introspection
===================

**Source code:** :source:`Lib/asyncio/stack.py`

-------------------------------------

asyncio has powerful runtime call stack introspection utilities
to trace the entire call graph of a running coroutine or task, or
a suspended *future*. These utilities and the underlying machinery
can be used by users in their Python code or by external profilers
and debuggers.

.. versionadded:: 3.14


.. function:: print_call_graph(*, future=None, file=None, depth=1)

Print the async call graph for the current task or the provided
:class:`Task` or :class:`Future`.

The function receives an optional keyword-only *future* argument.
If not passed, the current running task will be used. If there's no
current task, the function returns ``None``.

If the function is called on *the current task*, the optional
keyword-only *depth* argument can be used to skip the specified
number of frames from top of the stack.

If *file* is not specified the function will print to :data:`sys.stdout`.

**Example:**

The following Python code:

.. code-block:: python

import asyncio

async def test():
asyncio.print_call_graph()

async def main():
async with asyncio.TaskGroup() as g:
g.create_task(test())

asyncio.run(main())

will print::

* Task(name='Task-2', id=0x1039f0fe0)
+ Call stack:
| File 't2.py', line 4, in async test()
+ Awaited by:
* Task(name='Task-1', id=0x103a5e060)
+ Call stack:
| File 'taskgroups.py', line 107, in async TaskGroup.__aexit__()
| File 't2.py', line 7, in async main()

For rendering the call stack to a string the following pattern
should be used:

.. code-block:: python

import io

...

buf = io.StringIO()
asyncio.print_call_graph(file=buf)
output = buf.getvalue()


.. function:: capture_call_graph(*, future=None)

Capture the async call graph for the current task or the provided
:class:`Task` or :class:`Future`.

The function receives an optional keyword-only *future* argument.
If not passed, the current running task will be used. If there's no
current task, the function returns ``None``.

If the function is called on *the current task*, the optional
keyword-only ``depth`` argument can be used to skip the specified
number of frames from top of the stack.

Returns a ``FutureCallGraph`` data class object:

* ``FutureCallGraph(future, call_stack, awaited_by)``

Where 'future' is a reference to a *Future* or a *Task*
(or their subclasses.)

``call_stack`` is a list of ``FrameCallGraphEntry`` objects.

``awaited_by`` is a list of ``FutureCallGraph`` objects.

* ``FrameCallGraphEntry(frame)``

Where ``frame`` is a frame object of a regular Python function
in the call stack.


Low level utility functions
===========================

To introspect an async call graph asyncio requires cooperation from
control flow structures, such as :func:`shield` or :class:`TaskGroup`.
Any time an intermediate ``Future`` object with low-level APIs like
:meth:`Future.add_done_callback() <asyncio.Future.add_done_callback>` is
involved, the following two functions should be used to inform *asyncio*
about how exactly such intermediate future objects are connected with
the tasks they wrap or control.


.. function:: future_add_to_awaited_by(future, waiter, /)

Record that *future* is awaited on by *waiter*.

Both *future* and *waiter* must be instances of
:class:`asyncio.Future <Future>` or :class:`asyncio.Task <Task>` or
their subclasses, otherwise the call would have no effect.

A call to ``future_add_to_awaited_by()`` must be followed by an
eventual call to the ``future_discard_from_awaited_by()`` function
with the same arguments.


.. function:: future_discard_from_awaited_by(future, waiter, /)

Record that *future* is no longer awaited on by *waiter*.

Both *future* and *waiter* must be instances of
:class:`asyncio.Future <Future>` or :class:`asyncio.Task <Task>` or
their subclasses, otherwise the call would have no effect.
1 change: 1 addition & 0 deletions Doc/library/asyncio.rst
Original file line number Diff line number Diff line change
Expand Up @@ -99,6 +99,7 @@ You can experiment with an ``asyncio`` concurrent context in the :term:`REPL`:
asyncio-subprocess.rst
asyncio-queue.rst
asyncio-exceptions.rst
asyncio-stack.rst

.. toctree::
:caption: Low-level APIs
Expand Down
10 changes: 10 additions & 0 deletions Doc/library/inspect.rst
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,12 @@ attributes (see :ref:`import-mod-attrs` for module attributes):
| | | per-opcode events are |
| | | requested |
+-----------------+-------------------+---------------------------+
| | f_generator | returns the generator or |
| | | coroutine object that |
| | | owns this frame, or |
| | | ``None`` if the frame is |
| | | of a regular function |
+-----------------+-------------------+---------------------------+
| | clear() | used to clear all |
| | | references to local |
| | | variables |
Expand Down Expand Up @@ -310,6 +316,10 @@ attributes (see :ref:`import-mod-attrs` for module attributes):

Add ``__builtins__`` attribute to functions.

.. versionchanged:: 3.14

Add ``f_generator`` attribute to frames.

.. function:: getmembers(object[, predicate])

Return all the members of an object in a list of ``(name, value)``
Expand Down
6 changes: 6 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -417,6 +417,12 @@ asyncio
reduces memory usage.
(Contributed by Kumar Aditya in :gh:`107803`.)

* :mod:`asyncio` has new utility functions for introspecting and printing
the program's call graph: :func:`asyncio.capture_call_graph` and
:func:`asyncio.print_call_graph`.
(Contributed by Yury Selivanov and Pablo Galindo Salgado in :gh:`91048`.)


Deprecated
==========

Expand Down
2 changes: 2 additions & 0 deletions Include/cpython/genobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,8 @@ PyAPI_DATA(PyTypeObject) PyCoro_Type;
PyAPI_FUNC(PyObject *) PyCoro_New(PyFrameObject *,
PyObject *name, PyObject *qualname);

PyAPI_FUNC(void) _PyCoro_SetTask(PyObject *coro, PyObject *task);


/* --- Asynchronous Generators -------------------------------------------- */

Expand Down
7 changes: 7 additions & 0 deletions Include/internal/pycore_genobject.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,13 @@ extern "C" {
PyObject *prefix##_qualname; \
_PyErr_StackItem prefix##_exc_state; \
PyObject *prefix##_origin_or_finalizer; \
/* A *borrowed* reference to a task that drives the coroutine. \
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is adding yet another field generators for the use of async.
Remember that generators are used as iterators, probably more often than they are used for coroutines.
For code like any(expr for var in seq) and tuple(expr for var in seq) this is unwanted expense.

Is there no way to add this tasks, not generators?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Also, I don't see how it is safe for this to be a borrowed reference. What guarantee is there that the object passed to _PyCoro_SetTask will outlive the generator?

If the answer is "_PyCoro_SetTask is an internal function", then why is it used by asyncio which is a module?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is adding yet another field generators for the use of async.

AFAICT there is too much entanglement between generators and coroutines to split their implementation entirely to avoid this. How can we measure what this unwanted expense actually is?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Splitting their implementations is desirable, but I appreciate that it won't happen in this PR.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

cr_task is a borrowed reference, and is exposed through a public API, which is unsafe.
So it will need to made a strong reference.

That means that it not only does it takes space and needs initializing, it will also need to be cleared when the generator is destroyed.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@markshannon we agree with your feedback. I'll refactor the PR to remove the need for cr_task (and therefore the gi_task debug offset). We'll maintain a list of running tasks in _asynciomodule.c since this is asyncio-specific anyway.

The field is meant to be used by profilers and debuggers only. \
The main invariant is that a task can't get GC'ed while \
the coroutine it drives is alive and vice versa. \
Profilers can use this field to reconstruct the full async \
call stack of program. */ \
PyObject *prefix##_task; \
char prefix##_hooks_inited; \
char prefix##_closed; \
char prefix##_running_async; \
Expand Down
52 changes: 52 additions & 0 deletions Include/internal/pycore_runtime.h
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,10 @@ extern "C" {
#include "pycore_typeobject.h" // struct _types_runtime_state
#include "pycore_unicodeobject.h" // struct _Py_unicode_runtime_state

#if defined(__APPLE__)
# include <mach-o/loader.h>
#endif

struct _getargs_runtime_state {
struct _PyArg_Parser *static_parsers;
};
Expand Down Expand Up @@ -59,6 +63,37 @@ typedef struct _Py_AuditHookEntry {
void *userData;
} _Py_AuditHookEntry;

// Macros to burn global values in custom sections so out-of-process
// profilers can locate them easily

#define GENERATE_DEBUG_SECTION(name, declaration) \
_GENERATE_DEBUG_SECTION_WINDOWS(name) \
_GENERATE_DEBUG_SECTION_APPLE(name) \
declaration \
_GENERATE_DEBUG_SECTION_LINUX(name)

#if defined(MS_WINDOWS)
#define _GENERATE_DEBUG_SECTION_WINDOWS(name) \
_Pragma(Py_STRINGIFY(section(Py_STRINGIFY(name), read, write))) \
__declspec(allocate(Py_STRINGIFY(name)))
#else
#define _GENERATE_DEBUG_SECTION_WINDOWS(name)
#endif

#if defined(__APPLE__)
#define _GENERATE_DEBUG_SECTION_APPLE(name) \
__attribute__((section(SEG_DATA "," Py_STRINGIFY(name))))
#else
#define _GENERATE_DEBUG_SECTION_APPLE(name)
#endif

#if defined(__linux__) && (defined(__GNUC__) || defined(__clang__))
#define _GENERATE_DEBUG_SECTION_LINUX(name) \
__attribute__((section("." Py_STRINGIFY(name))))
#else
#define _GENERATE_DEBUG_SECTION_LINUX(name)
#endif

typedef struct _Py_DebugOffsets {
char cookie[8];
uint64_t version;
Expand Down Expand Up @@ -108,6 +143,7 @@ typedef struct _Py_DebugOffsets {
uint64_t instr_ptr;
uint64_t localsplus;
uint64_t owner;
uint64_t stackpointer;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stack pointer is not always readable, nor is it always correct, nor is it part of the ABI, and when it is correct or not may change at a whim.
In fact, it didn't even exist in 3.13, and may not exist again in future versions.

Overall, I think it is better not to publish this offset lest it suggest that the value is meaningful.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is needed because there is no other way to get the current running coroutine object from the frame with the current new layout from external profilers they can only read memory. Please check the tests.

If you have a better alternative I am happy to move to that but right now I don't see any other possibility

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The stack pointer is not always readable, nor is it always correct, nor is it part of the ABI, and when it is correct or not may change at a whim.

Can you point me at (pun not intended) a situation where the stack pointer will be invalid in a way that will make the external introspection (done in the tests here) not work?

In fact, it didn't even exist in 3.13, and may not exist again in future versions.

Is there a design that you have in mind that would result in the removal of this entirely?

AFAICT if we decided to drop deferred refcounting, we'd return to what stacktop was doing in the past, which was more-less equivalent for our purpose here. Moreover, @Fidget-Spinner claims the stack pointer is a better design regardless of deferred refcounting.

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In fact, it didn't even exist in 3.13, and may not exist again in future versions.

@markshannon Mark, I think the contract here is that CPython can move freely with its refactorings and changes. If stackpointer breaks or becomes completely non-meaningful we'll just mark our test "xfail" and figure another way to introspect the particular future version of CPython. I wouldn't worry about these offsets at all, we're not saying that they are public API.

Same as internal layout of Set/Dict/asyncio objects -- they are all subject to break from one version to another.

I really think this particular change is fine -- CPython doesn't care nor does guarantee anything whatsoever here.

Copy link
Member

@markshannon markshannon Oct 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Is there a design that you have in mind that would result in the removal of this entirely?

We experimented with a register VM. We ultimately decided against it, but if we had gone for it there would have been no stack pointer.

If stackpointer breaks or becomes completely non-meaningful we'll just mark our test "xfail"

The problem is that optimizations may leave the tests working, but the stack pointer could easily be meaningless in other places.
I would strongly recommend that you avoid relying on the stack pointer in any way.

We expect that the stack will consist of zero or more values in registers followed by the remaining values in memory, with the stack pointer pointing to some offset from the in-memory stack top.
The offset and number of values in registers will be not be knowable at runtime.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please check the tests.

@pablogsal
Where? I don't see where it is used.

Copy link
Member

@pablogsal pablogsal Oct 18, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I will copy It here for your convenience:

      uintptr_t stackpointer_addr;
         err = read_py_ptr(
             pid,
             coro_address + offsets->gen_object.gi_iframe +
                 offsets->interpreter_frame.stackpointer,
             &stackpointer_addr);
         if (err) {
             return -1;
         }

         if ((void*)stackpointer_addr != NULL) {
             uintptr_t gi_await_addr;
             err = read_py_ptr(
                 pid,
                 stackpointer_addr - sizeof(void*),
                 &gi_await_addr);
             if (err) {
                 return -1;
             }

as you can see is needed to go from the coroutine to the gi_await field.

} interpreter_frame;

// Code object offset;
Expand Down Expand Up @@ -152,6 +188,14 @@ typedef struct _Py_DebugOffsets {
uint64_t ob_size;
} list_object;

// PySet object offset;
struct _set_object {
uint64_t size;
uint64_t used;
uint64_t table;
uint64_t mask;
} set_object;

// PyDict object offset;
struct _dict_object {
uint64_t size;
Expand Down Expand Up @@ -192,6 +236,14 @@ typedef struct _Py_DebugOffsets {
uint64_t size;
uint64_t collecting;
} gc;

struct _gen_object {
uint64_t size;
uint64_t gi_name;
uint64_t gi_iframe;
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is an implementation detail, and may not exist in future versions of CPython

Copy link
Member

@pablogsal pablogsal Oct 17, 2024

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Everything in the debug offsets is an implementation detail so I don't think this made this special. Once more, this is used to follow the chain of coroutines so if it it changes we will adapt but the test we add ensures that there is an alternative and we don't change this in a way that breaks all profilers

Copy link
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, see also my comment here. I don't think it's worth anyones time to debate these offsets. Yes they are subject to break and that's fine, they are not a public API. Profilers and debuggers expect this kind of stuff to drift.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I worry that when they do break, we'll be pressured to restore whatever field they were relying on.

I'd be happier if these offsets were in their own header, with a large clear warning about breakage.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think which header file they live in should matter, we don't need a new header for this specific internal implementation detail, this already is such a header. Lets just add the desired code comment about stability expectations and breakage as instructions to our future selves that if these need to change, so be it, and that the internal implementation detail tests relying on these can be marked as expected failures at that time.

uint64_t gi_task;
uint64_t gi_frame_state;
} gen_object;
} _Py_DebugOffsets;

/* Reference tracer state */
Expand Down
15 changes: 15 additions & 0 deletions Include/internal/pycore_runtime_init.h
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ extern "C" {
#include "pycore_runtime_init_generated.h" // _Py_bytes_characters_INIT
#include "pycore_signal.h" // _signals_RUNTIME_INIT
#include "pycore_tracemalloc.h" // _tracemalloc_runtime_state_INIT
#include "pycore_genobject.h"


extern PyTypeObject _PyExc_MemoryError;
Expand Down Expand Up @@ -74,6 +75,7 @@ extern PyTypeObject _PyExc_MemoryError;
.instr_ptr = offsetof(_PyInterpreterFrame, instr_ptr), \
.localsplus = offsetof(_PyInterpreterFrame, localsplus), \
.owner = offsetof(_PyInterpreterFrame, owner), \
.stackpointer = offsetof(_PyInterpreterFrame, stackpointer), \
}, \
.code_object = { \
.size = sizeof(PyCodeObject), \
Expand Down Expand Up @@ -107,6 +109,12 @@ extern PyTypeObject _PyExc_MemoryError;
.ob_item = offsetof(PyListObject, ob_item), \
.ob_size = offsetof(PyListObject, ob_base.ob_size), \
}, \
.set_object = { \
.size = sizeof(PySetObject), \
.used = offsetof(PySetObject, used), \
.table = offsetof(PySetObject, table), \
.mask = offsetof(PySetObject, mask), \
}, \
.dict_object = { \
.size = sizeof(PyDictObject), \
.ma_keys = offsetof(PyDictObject, ma_keys), \
Expand Down Expand Up @@ -136,6 +144,13 @@ extern PyTypeObject _PyExc_MemoryError;
.size = sizeof(struct _gc_runtime_state), \
.collecting = offsetof(struct _gc_runtime_state, collecting), \
}, \
.gen_object = { \
.size = sizeof(PyGenObject), \
.gi_name = offsetof(PyGenObject, gi_name), \
.gi_iframe = offsetof(PyGenObject, gi_iframe), \
.gi_task = offsetof(PyGenObject, gi_task), \
.gi_frame_state = offsetof(PyGenObject, gi_frame_state), \
}, \
}, \
.allocators = { \
.standard = _pymem_allocators_standard_INIT(runtime), \
Expand Down
2 changes: 2 additions & 0 deletions Lib/asyncio/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@
from .protocols import *
from .runners import *
from .queues import *
from .stack import *
from .streams import *
from .subprocess import *
from .tasks import *
Expand All @@ -31,6 +32,7 @@
protocols.__all__ +
runners.__all__ +
queues.__all__ +
stack.__all__ +
streams.__all__ +
subprocess.__all__ +
tasks.__all__ +
Expand Down
Loading
Loading