Skip to content
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

Implement web.Runner context manager #8723

Open
wants to merge 10 commits into
base: master
Choose a base branch
from

Conversation

DavidRomanovizc
Copy link
Contributor

@DavidRomanovizc DavidRomanovizc commented Aug 16, 2024

What do these changes do?

These changes introduce the web.Runner context manager, based on asyncio.Runner. Added web.Runner.run_app method and refactored existing web.run_app to use web.Runner.run_app for running server. This feature preserves the backward compatibility

Are there changes in behavior for the user?

I think there aren't changes in behavior for users who will continue to use web.run_app as before; it remains fully compatible with existing code but users now have the option to use web.Runner.run_app

Is it a substantial burden for the maintainers to support this?

Supporting this change shouldn't be a substantial burden for maintainers because web.Runner builds on the asyncio.Runner with minor additions

Related to Issue: #8028

Checklist

  • I think the code is well written
  • Unit tests for the changes exist
  • Documentation reflects the changes (Before updating docs, I want to make sure that I'm on the right track)
  • If you provide code modification, please add yourself to CONTRIBUTORS.txt
    • The format is <Name> <Surname>.
    • Please keep alphabetical order, the file is sorted by names.
  • Add a new news fragment into the CHANGES/ folder

@psf-chronographer psf-chronographer bot added the bot:chronographer:provided There is a change note present in this PR label Aug 16, 2024
tests/test_runner.py Fixed Show fixed Hide fixed
tests/test_runner.py Fixed Show fixed Hide fixed
tests/test_runner.py Fixed Show fixed Hide fixed
tests/test_runner.py Fixed Show fixed Hide fixed
Copy link
Member

@Dreamsorcerer Dreamsorcerer left a comment

Choose a reason for hiding this comment

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

Looks like a good start. Still needs some fixes and documentation.

aiohttp/web.py Outdated Show resolved Hide resolved
aiohttp/web.py Outdated Show resolved Hide resolved
aiohttp/web.py Outdated Show resolved Hide resolved
aiohttp/web.py Outdated Show resolved Hide resolved
aiohttp/web.py Outdated Show resolved Hide resolved
aiohttp/web.py Outdated
Comment on lines 366 to 379
if (
threading.current_thread() is threading.main_thread()
and signal.getsignal(signal.SIGINT) is signal.default_int_handler
):
sigint_handler = functools.partial(self._on_sigint, main_task=task)
try:
signal.signal(signal.SIGINT, sigint_handler)
except ValueError:
# `signal.signal` may throw if `threading.main_thread` does
# not support signals (e.g. embedded interpreter with signals
# not registered - see gh-91880)
sigint_handler = None
else:
sigint_handler = None
Copy link
Member

Choose a reason for hiding this comment

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

I wonder if this SIGINT code needs to be copied to run_app()...
Will have to do some more testing.

aiohttp/web.py Outdated Show resolved Hide resolved
tests/test_runner.py Outdated Show resolved Hide resolved
tests/test_runner.py Outdated Show resolved Hide resolved
Copy link
Member

Choose a reason for hiding this comment

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

We'll also want some tests in test_run_app.py to verify the .run_app() method separately.
e.g. Maybe test that running a simple task followed by .run_app() works (and happens in the same loop).

@Dreamsorcerer
Copy link
Member

@bdraco Does this look reasonable overall?
I think after merging this, I'll make a followup PR to move all of run_app/Runner code to a separate file, as this one is getting a bit too big.

@Dreamsorcerer
Copy link
Member

Also, looking at the implementation, the .run_app() method only uses ._lazy_init() and ._loop from the class. So, I'm wondering if we should just ignore the comment about the class being final and subclass it anyway. Then just rely on decent tests to ensure we maintain compatibility.

@bdraco
Copy link
Member

bdraco commented Aug 21, 2024

Traveling today for the next 23 hours. Will take a look when I get to my destination

@bdraco
Copy link
Member

bdraco commented Aug 21, 2024

Looks like I'll be able to look today after all. Flight isn't happening due to engine issues so I'll be able to look once I get rebooked and back home.

@bdraco
Copy link
Member

bdraco commented Aug 21, 2024

@bdraco Does this look reasonable overall? I think after merging this, I'll make a followup PR to move all of run_app/Runner code to a separate file, as this one is getting a bit too big.

It looks reasonable to me. I want to subclass it instead of copying all the code, as keeping it in sync imposes some maintenance burden. Unfortunately, upstream explicitly says not to do that. We should add some comments about periodically checking upstream for bug fixes if we don't want to ignore that and do it anyways since we will have an out-of-sync copy as soon as anything changes.

@Dreamsorcerer
Copy link
Member

Dreamsorcerer commented Aug 22, 2024

Yeah, I think we should just subclass it, there's just too much code otherwise with tests duplicated etc. We only depend on lazy_init and the loop attribute from the class, so I think risk of breakage is low.

@DavidRomanovizc Would you be alright changing it to a subclass and removing the copied tests?
You'll need to define it with:

if sys.version_info >= (3, 11):
    class Runner(...

Then in web.run_app() also do a version check and use the new Runner code in 3.11+ or the old implementation otherwise (we'll then remove the old version when we drop support for 3.10).

@Dreamsorcerer Dreamsorcerer added the pr-unfinished The PR is unfinished and may need a volunteer to complete it label Sep 12, 2024
@DavidRomanovizc
Copy link
Contributor Author

Sorry for the long inactivity. Decided to rename Runner to WebRunner, inherited asyncio.Runner and added check version. In web.run_app returned old implementation and use the new Runner code if py version 3.11+ or the old impl otherwise

Copy link

codspeed-hq bot commented Oct 27, 2024

CodSpeed Performance Report

Merging #8723 will not alter performance

Comparing DavidRomanovizc:feature-runner (1f733e6) with master (dd9a1fd)

Summary

✅ 4 untouched benchmarks

@Dreamsorcerer Dreamsorcerer removed the pr-unfinished The PR is unfinished and may need a volunteer to complete it label Oct 27, 2024
class WebRunner(asyncio.Runner): # type: ignore
"""A context manager that controls event loop life cycle"""

def __init__(
Copy link
Member

Choose a reason for hiding this comment

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

Still a lot of copied code here. As far as I can tell, only the run_app() method needs to be defined here. init/close/_lazy_init can all be removed and just use the parent methods.

if not access_log.hasHandlers():
access_log.addHandler(logging.StreamHandler())

main_task = self._loop.create_task(
Copy link
Member

Choose a reason for hiding this comment

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

aiohttp/web.py Outdated Show resolved Hide resolved
INITIALIZED = "initialized"
CLOSED = "closed"

class WebRunner(asyncio.Runner): # type: ignore
Copy link
Member

Choose a reason for hiding this comment

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

We should probably get a new test that uses WebRunner directly too. Can probably just copy and modify one of the run_app() tests. The new test could also use WebRunner.run() to verify that the task and the app are run in the same loop.

@DavidRomanovizc
Copy link
Contributor Author

@Dreamsorcerer, I thought, why don't we replace inheritance with composition? We will still be able to use the functionality of asyncio.Runner. Also, we won't have to spend effort on maintenance because we ensure synchronization with the upstream

like that:

class WebRunner:
    def __init__(
        self,
        *,
        debug: Optional[bool] = None,
        loop_factory: Optional[Callable[[], asyncio.AbstractEventLoop]] = None,
    ):
        self.debug = debug
        self.loop_factory = loop_factory
        self._runner = asyncio.Runner(debug=debug, loop_factory=loop_factory)
   ...

Co-authored-by: Sam Bull <[email protected]>
@Dreamsorcerer
Copy link
Member

I thought, why don't we replace inheritance with composition?

We need to call _lazy_init(), right? So, we can't just rely on the public interface..

@Dreamsorcerer Dreamsorcerer added the pr-unfinished The PR is unfinished and may need a volunteer to complete it label Nov 14, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bot:chronographer:provided There is a change note present in this PR pr-unfinished The PR is unfinished and may need a volunteer to complete it
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants