-
-
Notifications
You must be signed in to change notification settings - Fork 2k
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
Use a new loop in run_app() #5572
Conversation
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'm not sure about this. The idea has always been that the current implicit loop gets used. OTOH the cleanup code resets the loop so maybe it's fine to do the same at the beginning too...
Do you mean
This seems odd to me. It gets the implicit loop, and then decides that it owns it? So, it then closes and resets the current loop. That seems surprising to me. If it wants to own a loop, then it should create that loop. Overall, I expect the function to behave much like |
@@ -491,6 +490,7 @@ def run_app( | |||
access_log.addHandler(logging.StreamHandler()) | |||
|
|||
try: | |||
asyncio.set_event_loop(loop) |
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 don't think it's a good idea to have it here. At least, put it right before try:
. The reason is that it's an antipattern to have more than one instruction in a try block (it tends to shadow exceptions sometimes and causes hard-to-debug situations).
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.
Yeah, I just noticed that it was inside the try block in asyncio.run()
, figured there was probably a good reason for it.
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.
Well, you could add a separate try/except if you want to call loop.close()
and re-raise. There's no need to asyncio.set_event_loop(None)
because setting it didn't happen anyway.
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've kept these 2 lines together in the try to match https://github.com/python/cpython/blob/main/Lib/asyncio/runners.py#L40-L44
But, I've moved the create_task() to before the try statement, as it doesn't seem to have any business being there.
The more I think about it, the more it seems like Andrew was talking about non- |
Co-authored-by: Sviatoslav Sydorenko <[email protected]>
Codecov Report
@@ Coverage Diff @@
## master #5572 +/- ##
=======================================
Coverage 96.75% 96.75%
=======================================
Files 44 44
Lines 9847 9849 +2
Branches 1590 1591 +1
=======================================
+ Hits 9527 9529 +2
Misses 182 182
Partials 138 138
Flags with carried forward coverage won't be shown. Click here to find out more.
Continue to review full report at Codecov.
|
Co-authored-by: Sviatoslav Sydorenko <[email protected]>
Think this should be ready now. |
web.run_app(app, path="/tmp/tmpsock.sock", backlog=10, print=stopper(patched_loop)) | ||
web.run_app( | ||
app, | ||
path="/tmp/tmpsock.sock", |
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.
Side note: ouch, this looks awfully hardcoded. It's probably a good idea to fix such hardcoded socket paths across tests (in a dedicated PR, of course).
Backport to 3.8: 💔 cherry-picking failed — conflicts found❌ Failed to cleanly apply 88d1f80 on top of patchback/backports/3.8/88d1f801903e132eca9736baf351861d60a9b4ad/pr-5572 Backporting merged PR #5572 into master
🤖 @patchback |
@Dreamsorcerer would you backport this? |
This change is NOT backward compatible and my App just get broken. If your app creates some asyncio tasks or store the current event loop for future usage before calling the |
Sorry for that, it is your oversight. The feature reversion in 3.8 makes even more mess, unfortunately. |
Yep, sorry, but it does sound like a very precarious way to write an application. I mean, you can't even use |
@Dreamsorcerer Actually the event loop in our app does not run aiohttp only, it also executes some other coroutines which are created before running the aiohttp, and we also defined a class level |
I second that! |
I'd say that |
Maybe we need async function that does almost all E.g. I recall a similar future request in the past. |
Possibly, but I've had similar discussions in other bug reports where users say they run other things in addition to the aiohttp app, just like @krizex. So far, everytime someone has actually elaborated on what they are doing, it turns out their code is brittle and should have been written with the aiohttp app being in full control and everything else running from application startup/cleanup_ctx. So, maybe this is actually more of a documentation issue, to encourage users to better understand how complex applications should be written. As an example, one of my apps requires a second server, which when running locally, I spawn in a separate process. The code for that looks something like:
If you need to run a long coroutine, then maybe something like:
Not doing it this way creates issues, with the app object not being in control of its own lifecycle, devtools failing to reload etc. I also can't see how you can write the code without using the low-level asyncio API, which is also discouraged. The above examples only use the high-level API. |
I think there's just confusion over how to write these kind of apps. It's the equivalent of writing a general asyncio application something like:
This is heavily discouraged and should obviously be written without the low-level API, using a main() function like:
In an aiohttp application, the app object becomes our main() function, which I think is what most users are missing. |
I'm ok with any decision, and don't want to invest my time in writing |
My application has broken, too. I would expect this kind of change in 4.0, according to semver best practices. I have nothing against the idea, but I am against backward-incompatible changes within the same major version.
I am using |
I'm sorry for uncovinience again and again. |
Yes, if I had realised there were several users actually using this behaviour, I wouldn't have backported it. Though, I can also argue that no high-level, documented behaviour has changed, you were depending on low-level implementation details for your app.
Which, again, is a low-level function. The documentation suggests you use the high-level
Which will also be broken. |
I agree with Sam: this is not a breaking change API-wise and it doesn't go against SemVer. SemVer operates the term "public API" which usually means a combination of function arguments and the return values — a contract, in the words. The implementation is a private thing and relying on the pre-existing loop was an internal detail, a side-effect. We are at mercy of the Hyrum's law1 here. This side-effect never was a part of the public API, we never declared that this quirk was supported, we never encouraged the users to rely on it. In fact, this is something that we would rather explicitly discourage as a design antipattern because of how much hard-to-debug harm it may bring. I think, though, we could improve the documentation to mention the recommendation to avoid such a design flaw. Footnotes
|
Shall I point at a hundred other "undocumented internal details" that everybody relies on implicitly? Your excuses are weird @webknjaz @Dreamsorcerer The words are fancy, but the impact is the same: you broke the workflow of many users, and new reports keep referencing this pull request. Anyway, the fix is very easy, so no drama. Very far from cosplaying GNOME developers. |
This has turned into another maintainer-blaming thread. Locking it to stop these insults. We've admitted that it was an oversight and provided an explanation of why this needs to remain as is. I'm not accepting any more complaints from people who don't actually care enough to take part in the ongoing development of aiohttp and only come here to complain — repeating the same thing over and over again is not constructive. With the behavior of complainers not piling up, this feels like a personal attack towards the maintainers who've actually spent countless hours maintaining this project. So I'm reminding you that we have a CoC. We welcome anyone to participate in constructive discussion but don't cross the line. P.S. The only action item I see currently is improving the docs. If anybody is up to it — feel free to send a PR. |
For anybody that has encountered this problem, we've updated the documentation with the above recommendations: https://docs.aiohttp.org/en/stable/web_advanced.html#complex-applications While the loop parameter provided a quick way to get an application working again, this was mainly included due to some internal testing code and is not recommended for public use. If you continue using your own loop with As for a low-level API, it looks like this is already covered by app runners. So, if you insist on using low-level functions and handling your own event loop, then use the app runners, not the high-level |
run_app() currently uses get_event_loop(), and basically relies on the side-effect of it creating a new loop. This causes issues if running code beforehand, for example with
asyncio.run()
as the side-effect will no longer happen and aRuntimeError
is raised.As the function is going to run the loop, there is no reason to think there would be a currently running loop, so I think it's safest to just create a new loop and set it as the current loop.