Uh oh!
There was an error while loading. Please reload this page.
Ensure get_event_loop returns the running loop when called in a coroutine - #355
Ensure get_event_loop returns the running loop when called in a coroutine#355vxgmichel wants to merge 18 commits into
Conversation
- get/set_event_loop is renamed to get/set_default_loop - get/set_running_loop is added - get_event_loop now uses the running loop if available, and the default loop otherwise The BaseEventLoopPolicy is updated, and adds a few features: - it does not allow to set a running loop if another one is already set - it issues warnings if the running loop doesn't correspond to the default loop A context manager is also added to AbstractEventLoopPolicy and AbstractEventLoop to set and unset the running loop in a safe manner. This might help for other event loop implementations.
| # Running context | ||
| def _running_context(self): |
There was a problem hiding this comment.
Don't think we need this as a separate method, this is something that users shouldn't ever call directly (even though it's a "private" name they will).
1st1
commented
Jun 3, 2016
@vxgmichel Thanks for working on this! There is a chance we can merge this before 3.5.2. Please update the PR. |
vxgmichel
commented
Jun 3, 2016
@1st1 I just removed the context manager and the warnings. Should I also revert the simplification for SleepTests and TimeoutTests? |
| # Non-abstract methods | ||
| def get_event_loop(self): | ||
| """Return the running event loop if any, and the default event |
There was a problem hiding this comment.
The first docstring line should be one-line sentence under 72 characters long (see https://www.python.org/dev/peps/pep-0257/#multi-line-docstrings). Please fix all docstrings that this PR adds/modifies.
Yes, absolutely. That code is there for a reason. I also don't think we need |
1st1
commented
Jun 3, 2016
Just realized that with |
1st1
commented
Jun 3, 2016
I think we need to add a new policy specifically for unittests (the majority of them), that would simply raise an exception in |
It seems like it used to be there for a reason, but not anymore since |
This reverts commit f117c75.
1st1
commented
Jun 3, 2016
Both should stay the same way. Now, the way the updated |
1st1
commented
Jun 3, 2016
In addition to the above: tests on Travis spit out this: |
OK I finally got it, we want to make sure the optional loop argument is never silently ignored. It makes sense, but it might be a problem in the long run. For instance, coroutines such as @coroutinedefsleep(delay, result=None):
loop=get_event_loop()
[...]So deftest_ctor_loop(self):
loop=mock.Mock()
lock=asyncio.Lock(loop=loop)
self.assertIs(lock._loop, loop)
lock=asyncio.Lock(loop=self.loop)
self.assertIs(lock._loop, self.loop)
deftest_ctor_noloop(self):
asyncio.set_event_loop(self.loop)
lock=asyncio.Lock()
self.assertIs(lock._loop, self.loop)Is that a workable solution or am I missing something? |
1st1
commented
Jun 3, 2016
That's debatable and out of the scope of this PR ;)
The correct solution is to create a new event loop policy for tests and update the tests to use it: classPolicy:
defget_event_loop(self):
raiseRuntimeError |
vxgmichel
commented
Jun 3, 2016
All right!
It turns that it's quite painful to do because of cyclic imports (a classTestCase(unittest.TestCase):
defset_event_loop(self, loop, *, cleanup=True):
assertloopisnotNone+# patch policy so get_event_loop doesn't return the running loop+policy=events.get_event_loop_policy()
+policy.get_event_loop=policy.get_default_loop+defclean_policy():
+try:
+delpolicy.get_event_loop+exceptAttributeError:
+pass+self.addCleanup(clean_policy)
# ensure that the event loop is passed explicitly in asyncioevents.set_event_loop(None)
ifcleanup:
self.addCleanup(loop.close)Is it acceptable? Can you think of a better solution? |
1st1
commented
Jun 3, 2016
I'd add a function like this to defdisable_get_event_loop(test_case):
assertisinstance(test_case, unittest.TestCase)
policy=events.get_event_loop_policy()
ifhasattr(policy, '_patched_get_event_loop'):
returndefget_event_loop():
raiseRuntimeError(
'asyncio.get_event_loop() is disabled in asyncio tests')
defreset_event_loop_method():
policy.get_event_loop=old_get_event_loopdelpolicy._patched_get_event_loopold_get_event_loop=policy.get_event_looppolicy.get_event_loop=old_get_event_looppolicy._patched_get_event_loop=Truetest_case.addCleanup(reset_event_loop_method)And then I'd add a call to it to Also, in of the earlier messages, I asked you to remove |
I just removed |
gvanrossum
commented
Jun 5, 2016
There's no way I can follow all the conversation that happened here. Why are we now collapsing get_event_loop() and get_running_loop() into one function? This seems to defeat the purpose of verifying in the unittests that there are no implicit dependencies on get_event_loop(). Should we just change all code that needs an event loop to assume get_event_loop() never raises? (E.g. sleep().) It's possible there's a good reason, but it better be explained well, not hidden in a PR. I think if we're really changing this we should have a more public debate on the tulip list. There are a lot of words devoted to this topic in PEP 3156, and I'd hate for the PEP to become out of date like this. |
Let's assume asyncdefcoro():
loop=asyncio.get_running_loop()
queue=asyncio.Queue(loop=loop)
[...]This is good, because asyncdefcoro():
queue=asyncio.Queue()
[...]and rely on the fact that using
It does change a few things in PEP 3156. What is said in Passing an Event Loop Around Explicitly still holds, but only outside the running loop. Inside,
It is indeed a big change that needs to be discussed, but I think the idea is worth considering. Here's the list of the coroutines in asyncio that could benefit from this:
|
| self._set_coroutine_wrapper(self._debug) | ||
| self._thread_id = threading.get_ident() | ||
| try: | ||
| policy.set_running_loop(self) |
There was a problem hiding this comment.
This has to be outside of the try statement.
There was a problem hiding this comment.
If we do that, this test (not commited yet) would fail:
deftest_run_loop_inside_loop(self):
@asyncio.coroutinedefcoro():
loop2=asyncio.new_event_loop()
self.assertRaises(RuntimeError, loop2.run_forever)
self.assertFalse(loop2.is_running()) # AssertionError: True is not falseloop2.close()
loop=asyncio.new_event_loop()
loop.run_until_complete(coro())
loop.close()There was a problem hiding this comment.
I'm beginning to think that this is all one big misunderstanding. I never
meant get_event_loop() to return a loop that's different from the running
loop. If you have multiple loops associated with the same thread you need
to implement a new EventloopPolicy that keeps track of which one is running
(e.g. via an explicit stack, or perhaps an implicit one using a local
variable to save the event loop and restoring from that in a finally
clause).
The only thing I meant to be possible is for get_event_loop() to raise an
exception, under special circumstances (especially unit tests that are
checking that no code accidentally relies on the default loop).
For library code I think there are two cases it should support:
- No default event loop, loop must always passed in
- Default event loop == current event loop, used when no loop is passed in
For application code I think it is totally fine to assume that
get_event_loop() returns the current, running loop. An application written
with this assumption never needs to pass a loop to something it calls
(since library code should always default to using get_event_loop()).
Note all the words devoted in the PEP to "context".
In any case I think we should stop coding and start discussing the use case
you are trying to address on the tulip list, to see whether it's real or
whether you are worrying too much (or whether maybe you should just write a
custom EventloopPolicy for your use case).
There was a problem hiding this comment.
@gvanrossum
I actually don't have a use case. I simply noticed that asyncio, 3rd party libraries and users tend to pass the running loop explicitely to their coroutines, because "explicit is better than implicit". That, in my opinion, is unecessary and could be simplified.
Sadly, such a simplification conflicts with the PEP and the way asyncio unittesting currently works. I understand that those constraints makes it hard, and maybe impossible to implement. I orginally meant this PR as a proposal and a starting point towards this simplification.
However, this idea didn't seem to get a lot of support and I don't feel like trying to push it forward anymore. Maybe more people will come up with the same rationale later on, and I'll be happy to help at that time.
There was a problem hiding this comment.
I'm beginning to think that this is all one big misunderstanding. I never
meant get_event_loop() to return a loop that's different from the running
loop. If you have multiple loops associated with the same thread you need
to implement a new EventloopPolicy that keeps track of which one is running
(e.g. via an explicit stack, or perhaps an implicit one using a local
variable to save the event loop and restoring from that in a finally
clause).
But here's the problem: if you have multiple loops associated with the same thread, policy doesn't know which one is currently running.
That's why I like the idea of adding policy.set_running_loop and policy.get_running_loop functions, because it makes policies more capable and allows you to worry less about multiple running loops event with the default policy.
There was a problem hiding this comment.
if you have multiple loops associated with the same thread, policy doesn't know which one is currently running.
Then write a new policy. The whole point of having policies is that you can write different ones. The default policy does not really cater to this use case, but a policy is a really simple object.
There was a problem hiding this comment.
Without set_running_loop and get_running_loop how will the policy know precisely what's going on? All I'm saying is that get_event_loop and set_event_loop are kind of detached from the loop's lifecycle; policy doesn't receive any notification if a loop was stopped or run again.
There was a problem hiding this comment.
ambv
commented
Jun 6, 2016
Correct me if I'm wrong but it seems to me the reason of this entire debate is as simple as:
We see this pattern emerging internally at Facebook because:
Sadly, as I said, having to pass the loop around everywhere is tiringly verbose and brittle (all it takes is for me to forget to do it once and I'm back on the default loop again). The current state of things reminds me of logging frameworks that require you to pass the logger around everywhere. Correct but brittle and inconvenient.
|
I'm not so sure about the "what is the current coroutine runner" proposal. It seems that Ben wants the user to call it and depending on the result decide what to use --
If the task is created manually, |
ambv
commented
Jun 6, 2016
I'm not worried about asyncio.Task instances created by hand. If you use the "low-level" approach, you're responsible for it. I'm a little worried about code like: It'd be helpful if For unit tests for example, I think it would be enough if |
ambv
commented
Jun 6, 2016
One case where we found the current So... I'm warming up to the idea that we should stop worrying (and passing |
gvanrossum
commented
Jun 6, 2016
On Mon, Jun 6, 2016 at 12:21 PM, Łukasz Langa notifications@github.com
Yes! Or write your own event loop policy (easier than you think, it's a really --Guido van Rossum (python.org/~guido) |
This is something I did not realize about The following example lists the different use cases: |
gvanrossum
commented
Jun 9, 2016
via email
Maybe. I'd be more for documenting this better though. |
vxgmichel
commented
Jun 13, 2016
@1st1 I guess I can close this PR right? By the way, a somehow related question has been asked on stackoverflow. |
1st1
commented
Jun 13, 2016
@vxgmichel Yes, let's close this PR for now. We should indeed focus on the docs; want to work on that? ;) |
1st1
commented
Nov 3, 2016
@gvanrossum Guido, are you OK if I merge this PR? (as per https://groups.google.com/d/msg/python-tulip/yF9C-rFpiKk/tk5oA3GLHAAJ) |
gvanrossum
commented
Nov 3, 2016
If you're asking do I want this feature? Yes. If you're asking me is this PR perfect? I have no idea, but I trust your review. (I don't want to have to re-understand the long discussion we had here earlier.) |
1st1
commented
Nov 3, 2016
Thank you. I'll spend some time testing the patch (with uvloop tests too) and will merge it soon. |
vxgmichel
commented
Nov 3, 2016
@1st1 A few month ago, @gvanrossum wrote:
This made me realize the whole defrun_forever(self):
try:
ifasyncio.get_event_loop() !=self:
raiseRuntimeError('Loop not approved by the policy')
exceptasyncio.NoLoopSetError:
# Now running in 'explicit' mode, used for unit-testingpasselse:
# The loop has been approved by the policypassThen we can advertise in the docs that Would that work, or I am missing something? |
asvetlov
commented
Nov 3, 2016
People may write Without changing default loop or even with disabling it by |
1st1
commented
Nov 3, 2016
@vxgmichel Please read the thread: https://groups.google.com/d/msg/python-tulip/yF9C-rFpiKk/tk5oA3GLHAAJ. The point is to make So we want to modify one single aspect of The point of modifying Since I like the design of the current PR. |
gvanrossum
commented
Nov 3, 2016
via email
So it'll be up to the policy to make get_event_loop() return the running
loop if one is set and the other one if not, right? And there won't be a
public interface for get_running_loop().
Still it's an API change -- policies need to implement
get/set_running_loop(). What if someone has a custom policy that doesn't
implement these and doesn't inherit from AbstractEventLoopPolicy? (It could
still be "an instance of" it, using the ABC registration API.) I wonder if
this would be a PEP change.
There could be a different way to implement this, making the "running" loop
a thread-local that is returned by the global get_event_loop() function in
preference over calling the policy's event loop. |
I like this approach more. Does this make sense to you:
This way there is no API change for policies, and it's easy to add support of this to third-patry loop implementations. I think I'll close this PR and make a new one implementing this strategy. |
gvanrossum
commented
Nov 3, 2016
via email
Sounds good. Maybe name the new APIs get/set_running_loop() or
get/set_active_loop()? To emphasize that this is the loop used by all
coroutines. Also maybe these should be named _get_*() (i.e. an underscore
prefix) to clarify that they should only be called by event loop
implementations, not by users. Or at least for the _set_ variant. |
asvetlov
commented
Nov 3, 2016
I vote on underscores for both getter and setter -- this API is really private. |
This PR follows up on the issue 26969.
It is meant to ensure that
asyncio.get_event_loopreturns the running loop when called in a coroutine (or loop callbacks). This could simplify all the coroutines that includes aloopoptional argument by removing it.This is done by modifying the interface of
AbstractEventLoopPolicy:get/set_event_loopis renamed toget/set_default_loopget/set_running_loopis addedget_event_loopnow uses the running loop if available, and the default loop otherwiseBaseEventLoopPolicyis updated, and adds a few features:A context manager is also added to
AbstractEventLoopPolicyandAbstractEventLoopto set and clear the running loop in a safe manner. This might help for other event loop implementations.