-
-
Notifications
You must be signed in to change notification settings - Fork 30.3k
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
gh-121621: Move asyncio running loop to thread state #121695
gh-121621: Move asyncio running loop to thread state #121695
Conversation
CC @markshannon |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think it might be worthwhile to split this up into two PRs:
- The running loop change, which should be small and relatively straightforward
- The freelist change, which I think needs more work to avoid duplicating struct definitions
Modules/_asynciomodule.c
Outdated
@@ -3816,12 +3814,15 @@ module_traverse(PyObject *mod, visitproc visit, void *arg) | |||
Py_VISIT(state->context_kwname); | |||
|
|||
// Visit freelist. | |||
PyObject *next = (PyObject*) state->fi_freelist; | |||
#ifdef WITH_FREELISTS |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem right. We don't visit freelists anywhere else. The objects in the freelist should never be tracked by the GC.
Include/internal/pycore_freelist.h
Outdated
@@ -147,6 +156,39 @@ extern void _PyAsyncGen_ClearFreeLists(struct _Py_object_freelists *freelists, i | |||
extern void _PyContext_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization); | |||
extern void _PyObjectStackChunk_ClearFreeList(struct _Py_object_freelists *freelists, int is_finalization); | |||
|
|||
// Keep in sync with _asynciomodule.c ! | |||
typedef struct futureiterobject_dummy { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think we want to avoid this sort of duplication if at all possible.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
A few suggestions:
I think the get_running_loop
and set_running_loop
are more confusing than helpful at this point. For example, there's a bunch of dead code now to access the module state. I think the code will be easier to read if they are inlined into their callsites.
I think the code will be more robust if the loop
field on PyThreadState
uses NULL
instead of Py_None
(i.e., handle the conversion to Py_None
in the call sites as necessary). PyThreadState_Clear()
still sets the field to NULL
, which breaks the non-NULL invariant -- PyThreadState_Clear
can still execute Python code via finalizers.
I agree with the first suggestion. The second suggestion has one problem --- we'd still need to special case Py_None because the asyncio spec says if the event loop is None, it will raise a RuntimeError. So we'd need an extra branch checking for NULL and converting to Py_None, AND a branch checking if it's non-null but Py_None. That's not clean IMO. I'd prefer we just set it to Py_None in PyThreadState_Clear to keep the invariant that there are only two states: Py_None and not-none. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This LGTM. I left a few minor style comments.
...we'd still need to special case Py_None because the asyncio spec says if the event loop is None, it will raise a RuntimeError.
This is a common pattern in CPython. We normalize Py_None
to NULL
in the setter. For example, _asyncio__set_running_loop
would look like:
PyThreadState *ts = _PyThreadState_GET();
if (loop == Py_None) {
loop = NULL;
}
Py_XSETREF(ts->asyncio_running_loop, Py_XNewRef(loop));
For example, see:
Lines 722 to 723 in 8f25321
if (value == Py_None) | |
value = NULL; |
This is a much more common pattern than setting the field to Py_None
in the clear function.
Thanks @Fidget-Spinner for the PR 🌮🎉.. I'm working now to backport this PR to: 3.13. |
GH-121864 is a backport of this pull request to the 3.13 branch. |
Follow up to #121622