-
Notifications
You must be signed in to change notification settings - Fork 5.5k
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
Revert some deprecations, following asyncio changes #3223
Revert some deprecations, following asyncio changes #3223
Conversation
I think the CI failure is something random unrelated to these changes. It's failing in a test about subprocess handling, only on Python 3.10, and only with |
Thanks! These changes look good, but to be sure I want to wait until we can test with the next alpha of python 3.12 (due next week) before merging. The CI issue is a known flaky test which I've just written up as #3225. We should probably just skip that test since it's not helpful to trip over it every once in a while.
In the test classes, I think we can just revert that whole block from https://github.com/tornadoweb/tornado/pull/3097/files#diff-0c44c25deecc01d7f48e0fd36005e637bb772273e31377f21fb16690c285dc4b - the old code should work again. But this is something we'd need to test with the new alpha. AnyThreadEventLoopPolicy's deprecation comment is still valid. In general I think we're probably fine with removing the deprecation notices entirely. We'll explain the undeprecations in the 6.3 release notes and amend the ones from 6.2 to note what's changed again. |
Thanks! No problem about waiting for the next 3.12 alpha. From this discussion, it looks like the behaviour changes (not creating a default event loop for the main thread) are being delayed to 3.14, because the deprecation warnings weren't thorough enough. I've changed
I'm still a bit confused about this. The comment added in that PR says "packages such as pytest-asyncio (as of version 0.16.0) still rely on the implicit current event loop and we want to be compatible with them". But I think reverting to the code before that will always create a new event loop (calling It's unfortunate that there appears to be no way of getting the future (Python 3.14) behaviour now - there's no public attribute or method to check if the current thread already has an event loop without potentially creating a new one. |
The new 3.12 alpha is out and #3230 enables it in CI, so that will be picked up when you rebase/merge this PR. I've also been discussing the deprecation plans in python/cpython#100160 (comment) to confirm that we're on the right track.
Guido is open to adding such a method to 3.12. I thought I needed one when I was working on #3230, but figured out it was obsolete. I need to look back at the testing change to see whether we might need one there. |
4e4b164
to
2a5de1f
Compare
OK, I've rebased, and all the tests seem to be passing with the new 3.12 alpha. |
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 still a bit confused about this. The comment added in that PR says "packages such as pytest-asyncio (as of version 0.16.0) still rely on the implicit current event loop and we want to be compatible with them". But I think reverting to the code before that will always create a new event loop (calling IOLoop()) and set it as the current one.
Yes, this is confusing, and my brief comments left me struggling to piece together what's going on. But the key is to look at GetNewIOLoopTest. Reverting to the older code will call self.get_new_ioloop()
every time, which will normally create a new event loop and make it current. But that method can be overridden, and sometimes must be, to allow some surrounding framework (like pytest-asyncio) to initialize an event loop that will be used in the test.
As long as reverting to the previous version of the testing code passes GetNewIOLoopTest, I think that's all we need to do here.
@@ -254,10 +254,13 @@ def current(instance: bool = True) -> Optional["IOLoop"]: # noqa: F811 | |||
""" | |||
try: | |||
loop = asyncio.get_event_loop() | |||
except (RuntimeError, AssertionError): | |||
except RuntimeError: |
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.
Are you removing AssertionError
here based on this comment that says AssertionError
was only used in Python 3.4?
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.
Yup, that's what I went on.
tornado/ioloop.py
Outdated
@@ -257,7 +258,9 @@ def current(instance: bool = True) -> Optional["IOLoop"]: # noqa: F811 | |||
except RuntimeError: | |||
if not instance: | |||
return None | |||
# Create a new asyncio event loop for this thread. | |||
if threading.current_thread() is not threading.main_thread(): |
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 we want the main-thread-only behavior here. Tornado has historically allowed event loop creation on any thread (that's what AnyThreadEventLoopPolicy is for), so it's a nice side effect that we can bring back the "any thread" behavior by default at the same time that AnyThreadEventLoopPolicy is being deprecated.
Just remove this commit.
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.
OK, I've removed that commit, and adjusted AnyThreadEventLoopPolicyTest
to match.
2a5de1f
to
e74e256
Compare
I reverted the code in |
Ugh, I'm having a hard time understanding how the current machinery (current, make_current, clear_current) is meant to work without a way to check asyncio for a 'current' event loop. With the more precise deprecation warning in Python 3.11.1, we could convert DeprecationWarning to an error, catch it the same way as RuntimeError, and interpret it as 'no existing event loop'. But in 3.11.0 (and several 3.10.x versions), this will also stop us getting an explicitly set loop (i.e. set by Or we could just silence the deprecation warnings; either for the tests, or for anything calling tornado's ioloop API. 😕 |
c722bcd
to
6772f90
Compare
For now, I've gone back to suppressing the deprecation warning for large parts of the tests. |
Lines 256 to 258 in 9ea5cdd
But no matter what that comment says, we still use this in a couple of places internally.
I'd be willing to lose the error check in
We have the opportunity to add a So let's step back a minute. Maybe we don't need to undeprecate everything here. There's no reason for Tornado to have its own |
This does appear to work, except that it breaks the poorly-understood pytest-asyncio interactions. Interestingly, the standard library's
Does that mean to use IsolatedAsyncioTestCase instead of pytest-asyncio, or that pytest-asyncio works with IsolatedAsyncioTestCase but not with plain unittest.TestCase? I think it's the former; I don't see any tests or examples of pytest-asyncio being used in conjunction with IsolatedAsyncioTestCase. Given that precedent, I'm inclined to just decide that it's not feasible to combine pytest-asyncio with Tornado's AsyncTestCase in this way. (This may get easier since pytest-asyncio appears to have recently switched to a "strict" mode in which it only activates on annotated test methods instead of running on all of them). |
That sounds reasonable to me. Having fewer pieces that deal with mutable global state seems like a good idea. So the suggested replacements would be:
If we're deprecating the
I read it as the latter. I think they're saying that if you prefer to write your tests unittest style (subclassing a TestCase class) but run them with pytest, then you should subclass from one of those options in your test code, and use the pytest-asyncio plugin to run them. This fits with pytest-dev/pytest-asyncio#180 .
Indeed, that has made things a lot nicer for compatibility. So the plan is that |
I've had a go at these changes.
I figured that if we were deprecating |
tornado/ioloop.py
Outdated
.. versionchanged:: 6.3 | ||
``make_current=False`` is now the default when creating an IOLoop - | ||
previously the default was to make the event loop current if there wasn't | ||
already a current one. Passing ``make_current=True`` is deprecated. |
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 just noticed that make_current=True
was already deprecated in 6.2; I can split this out in the docstring if you like.
tornado/testing.py
Outdated
with warnings.catch_warnings(): | ||
warnings.simplefilter("ignore", DeprecationWarning) | ||
self.io_loop.make_current() | ||
self._prev_aio_loop = asyncio.get_event_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 think this maintains compatibility with what pytest-asyncio does? At least, it passes the test that appears to be checking this.
It will create an unnecessary event loop when there isn't already a current one. But after the first test has done this, there will be a current event loop, so subsequent tests will only create and tear down the one they're using.
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 is sneaky - it works in tornado's test suite because we configure deprecation warnings to be errors. But in a default configuration, it will log a warning and there's no good way to suppress it. (This makes me think that we probably need a CI configuration that doesn't have warnings-as-errors, just like we have one that runs in -O mode to ensure we don't rely on the assert statement which can be disabled).
If the pytest-asyncio integration were valuable enough I think we could figure out a way to keep it and deal with the warning, but given the precedent of IsolatedAsyncioTestCase I think we're better off just simplifying it. The person who originally asked for this feature in #2324 says they no longer need it, and I suspect that the reason they needed it in the first place was something idiosyncratic about their application rather than something broadly applicable to all pytest users.
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.
In this setUp
method, we're specifically silencing those deprecation warnings (if triggered by tornado code). As I understand the filtering system, this should hide them for anyone using AsyncTestCase, regardless of what options they set in their own code.
However, if it's no longer needed now, we can simplify the code to just clear the event loop, without storing the previous one. I think that means taking out GetNewIOLoopTest
as well.
Yes, that's the most direct translation, although even better would be to make the asyncio loop the top-level object instead of reaching into the IOLoop to get it.
Yes. One other caveat is that in asyncio, you can't completely reset the state of the global event loop. The implicit event loop creation only works if set_event_loop has never been called (this is why you needed to delete a call to set_event_loop(None) earlier in this PR). Setting it to None doesn't bring back the default behavior, so in some cases it might be more compatible to use
We want to get away from all the "current" methods as near-equivalents for set_event_loop. But in the case of the IOLoop constructor, we create a new event loop, and the question is whether we call In any case, I think the IOLoop constructor is essentially soft-deprecated - no new code should use it, although it would be too disruptive to remove it (of course we must have some constructor, so by "remove" I mean make it for internal use of
Yes. We want to follow IsolatedAsyncioTestCase in this respect, and it does set_event_loop(None) in tearDown. |
9d345b2
to
65cd10a
Compare
I'm conscious that you might be spending more time explaining to me what changes should happen than it would take you to make them yourself. Also, I think the timezones mean we're usually taking a day for each exchange of messages. If you prefer to make the changes directly, go for it. I've ticked the checkbox that lets you push to my branch, or of course you can make a new branch. I started this because it seemed like a relatively straightforward piece of busywork that I could take care of for you. 🙂 |
I'm also conscious of that possibility, but I don't think it's been a problem yet. I appreciate your help here even though it didn't turn out to be a "straightforward piece of busywork". However, now that we're at the finish line I think I'll take over to do the last few cleanups (including a rebase of the commit history). I'm going to remove the silence of deprecation warnings in AsyncTestCase.setUp, deprecate get_new_ioloop, and add a deprecation warning in clear_current. |
Actually, the commit history looks pretty decent and rebasing anything looks like more trouble than it's worth (and github is not letting me push to your branch), so I'll merge this and do a quick followup PR for the last couple of changes. Thanks again! |
Thank you! |
This is an attempt to tackle #3216 - at least the straightforward parts of it.
make_current
andclear_current
methods, and themake_current
constructor parameter.AsyncTestCase
andAsyncHTTPTestCase
bind
&start
deprecation notices (but they're still deprecated)I've assumed that removing the deprecation notices is sufficient documentation - we could add notes saying that they were deprecated and then undeprecated again, but that feels like unnecessary clutter.
Still to figure out:
IOLoop.current()
has a deprecation notice for using it without a running asyncio loop. Is this also un-deprecated? Does it need some code change to handle when there's no running loop nor a default loop specifically set in asyncio?tornado/tornado/testing.py
Lines 204 to 238 in 5953601
AnyThreadEventLoopPolicy
remains deprecated (as mentioned in Reverse some deprecation warnings from Tornado 6.2 #3216), but is any change needed to its deprecation notice?tornado/tornado/platform/asyncio.py
Lines 423 to 431 in 5953601