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 3 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
import asyncio
import contextvars
import re
import unittest

Check notice

Code scanning / CodeQL

Module is imported with 'import' and 'import from' Note test

Module 'unittest' is imported with both 'import' and 'import from'.
try:
await asyncio.sleep(0.1)
except asyncio.CancelledError:
1 / 0

Check notice

Code scanning / CodeQL

Statement has no effect Note test

This statement has no effect.
async for the_meaning_of_life in spinner:
pass
except asyncio.CancelledError:
1 / 0

Check notice

Code scanning / CodeQL

Statement has no effect Note test

This statement has no effect.
cvar.set(val)
return old

async def get_context():

Check notice

Code scanning / CodeQL

Unused local variable Note test

Variable get_context is not used.
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 Show resolved Hide resolved
Comment on lines +421 to +422
self._loop.set_debug(self._debug)

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 already done in _laze_init().

Suggested change
self._loop.set_debug(self._debug)

Comment on lines +456 to +457
if self._set_event_loop:
asyncio.set_event_loop(self._loop)
Copy link
Member

Choose a reason for hiding this comment

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

Also in _lazy_init()

Suggested change
if self._set_event_loop:
asyncio.set_event_loop(self._loop)

Comment on lines +462 to +466
_cancel_tasks({main_task}, self._loop)
_cancel_tasks(asyncio.all_tasks(self._loop), self._loop)
self._loop.run_until_complete(self._loop.shutdown_asyncgens())
self.close()
asyncio.set_event_loop(None)
Copy link
Member

Choose a reason for hiding this comment

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

Already done in .close():

Suggested change
_cancel_tasks({main_task}, self._loop)
_cancel_tasks(asyncio.all_tasks(self._loop), self._loop)
self._loop.run_until_complete(self._loop.shutdown_asyncgens())
self.close()
asyncio.set_event_loop(None)
_cancel_tasks({main_task}, self._loop)
self.close()

aiohttp/web.py Show resolved Hide resolved
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.

Comment on lines +689 to +695
if loop is not None:

def loop_factory():
return loop

else:
loop_factory = events.get_running_loop
Copy link
Member

Choose a reason for hiding this comment

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

I think loop_factory should default to None (maybe this is the cause of the RuntimeError above?).

Suggested change
if loop is not None:
def loop_factory():
return loop
else:
loop_factory = events.get_running_loop
loop_factory = None if loop is None else lambda: loop

tests/test_runner.py Show resolved Hide resolved
tests/test_runner.py 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
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