Thanks to visit codestin.com
Credit goes to github.com

Skip to content

gh-110771: Decompose run_forever() into parts #110773

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 12 commits into from
Oct 13, 2023

Conversation

freakboy3742
Copy link
Contributor

@freakboy3742 freakboy3742 commented Oct 12, 2023

Decomposes run_forever() into a run_forever_setup(), the actual loop, and run_forever_cleanup(), so that the CPython asyncio event loop can be easily integrated with other event loops.

Refactors the Winforms ProactorEventLoop's run_forever() to make use of this new API entry point.

I don't believe any additional testing is required, as coverage inside Python won't change.

Fixes #110771.

@gvanrossum
Copy link
Member

Since you’re planning to use this, you do need tests and docs.

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

Next, tests, I presume?

Comment on lines 605 to 606
"""Set up an event loop so that it is ready to start actively looping
to process events.
Copy link
Member

Choose a reason for hiding this comment

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

By convention the first paragraph of a docstring must be a one-line summary.

Suggested change
"""Set up an event loop so that it is ready to start actively looping
to process events.
"""Prepare for processing events.

Comment on lines 608 to 609
Returns the state that must be restored when the loop concludes. This state
should be passed in as arguments to ``run_forever_cleanup()``.
Copy link
Member

Choose a reason for hiding this comment

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

Document the type of the state, please. (If it's meant to be opaque, make it more opaque than a 1-tuple please. :-)

I find it a bit odd that the method name is "run_forever_setup()", which doesn't hint at the existence of a return value. (Could you store the state to be restored in a private/protected instance variable instead of returning it?)

Also, "arguments"? Or "argument"?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I've modified the implementation to store the state as a protected variable, which simplifies the interface.

Returns the state that must be restored when the loop concludes. This state
should be passed in as arguments to ``run_forever_cleanup()``.

This method is only needed if you are writing your own event loop, with
Copy link
Member

Choose a reason for hiding this comment

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

"is only needed if" -- odd phrasing. The method exists. Do you mean it only needs to be overridden? Maybe show a brief example of how it's supposed to be used in that case? (I intentionally didn't the docs before reading the code, and from only reading the docstring I'm a bit confused about what's hooking what. From reading more code I realize that setup/cleanup are what you intend to override.)

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No - it would be run_forever() that is being replaced. I've provided a sample implementation in the docs; I'll clarify the language here to (hopefully) make the intended usage more clear.

Comment on lines 631 to 632
"""Clean up an event loop after the event loop finishes the looping over
events.
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
"""Clean up an event loop after the event loop finishes the looping over
events.
"""Clean up after the event loop finishes the looping over events.

sys.set_asyncgen_hooks(*old_agen_hooks)

def run_forever(self):
"""Run until stop() is called."""
Copy link
Member

Choose a reason for hiding this comment

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

Maybe clarify in this docstring that this is what calls setup/cleanup?

processing events.

Returns the state that must be restored when the loop concludes. This state
should be passed in as arguments to :meth:`loop.run_forever_cleanup()`.
Copy link
Member

Choose a reason for hiding this comment

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

"argument" I presume?

Suggested change
should be passed in as arguments to :meth:`loop.run_forever_cleanup()`.
should be passed in as argument to :meth:`loop.run_forever_cleanup()`.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No longer relevant as I've moved this to be an internal variable.


.. note::

This method is only needed if you are writing your own ``EventLoop``
Copy link
Member

Choose a reason for hiding this comment

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

Maybe this? Or maybe I misunderstand and the subclass would call this and the cleanup method?

Suggested change
This method is only needed if you are writing your own ``EventLoop``
Overriding this method is only needed if you are writing your own ``EventLoop``

Copy link
Contributor Author

Choose a reason for hiding this comment

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

The usage I'm envisaging (and the usage that would be needed by Toga would be to call the startup/cleanup methods, but provide a custom "while True: process event" loop that does whatever is needed for integration.

Overriding may also be required - the Windows event loop does this. However, I suspect that's more a case of Python's implementation using subclassing to satisfy the needs of all platforms, rather than something you'll see with GUI integration.

you are integrating Python's asyncio event loop with a GUI library's event
loop, you can use this method to ensure that Python's event loop is
correctly configured and ready to start processing individual events. Most
end users will not need to use this method directly.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe stronger?

Suggested change
end users will not need to use this method directly.
end users should not call this method directly.

Also below.

Also, I'd like to see a tiny example of how to write such a subclass.

Perform any cleanup necessary at the conclusion of event processing to ensure
that the event loop has been fully shut down.

The *original_state* argument is the return value provided by the call to
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
The *original_state* argument is the return value provided by the call to
The *original_state* argument is the return value from the call to

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No longer relevant as I've moved this to be an internal variable.

you are integrating Python's asyncio event loop with a GUI library's event
loop, you can use this method to ensure that Python's event loop has been
fully shut down at the conclusion of processing events. Most end users
will not need to use this method directly.
Copy link
Member

Choose a reason for hiding this comment

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

Same as above.

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

Getting very close...

.. method:: loop.run_forever_cleanup()

Perform any cleanup necessary at the conclusion of event processing to ensure
that the event loop has been fully shut down.
Copy link
Member

Choose a reason for hiding this comment

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

Maybe say something about whether this is re-entrant? Should I worry about not calling it twice?

Comment on lines 244 to 245
self.run_forever_cleanup()
gui_library.cleanup()
Copy link
Member

Choose a reason for hiding this comment

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

I would imagine these two should happen in the reverse order? Cleanup in reverse order of setup. (Also, to be more robust, I'd use multiple nested try/finallys, but the example doesn't need that complexity.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I'll reverse the order; agreed that another layer of try/finally is probably overkill for documenation purposes.

Comment on lines 239 to 241
self._run_once()
gui_library.process_events()
if self._stopping:
Copy link
Member

Choose a reason for hiding this comment

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

It's unfortunate you need to use private (technically "protected") API and variable here.
Then again, maybe "private" is the right term and we're okay with subclasses using those.

It's also unfortunate that this documentation now constrains what we can do in run_forever().
It almost seems that we might as well document what run_forever() does and specify it as never doing anything else.

@freakboy3742
Copy link
Contributor Author

Following conversation with @1st1 - making the setup/cleanup methods public API is a bit risky, as it locks us in to specific API designs around run loop operation.

He suggested that doing the refactoring as a protected APIs was fine is a useful convenience for people who are subclassing in full knowledge that they're messing with internals.

Copy link
Member

@gvanrossum gvanrossum left a comment

Choose a reason for hiding this comment

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

LGTM! Thanks for indulging me. Sorry to send you on a detour. Glad @1st1 knew what to do.

@freakboy3742 freakboy3742 deleted the decomposed-event-loop branch October 13, 2023 14:13
freakboy3742 added a commit to freakboy3742/toga that referenced this pull request Nov 8, 2023
aisk pushed a commit to aisk/cpython that referenced this pull request Feb 11, 2024
Effectively introduce an unstable, private (really: protected) API for subclasses that want to override `run_forever()`.
freakboy3742 added a commit to freakboy3742/toga that referenced this pull request Feb 22, 2024
mhsmith added a commit to beeware/toga that referenced this pull request Feb 22, 2024
Glyphack pushed a commit to Glyphack/cpython that referenced this pull request Sep 2, 2024
Effectively introduce an unstable, private (really: protected) API for subclasses that want to override `run_forever()`.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Decompose asyncio.run_forever() into parts to improve integration with other event loops
2 participants