-
-
Notifications
You must be signed in to change notification settings - Fork 178
Ensure get_event_loop returns the running loop when called in a coroutine #355
Changes from all commits
b30bcdc
705a54c
2e55a60
f117c75
1c3a166
6e8eaa2
be2c0f1
c1cb666
8788c72
121cca3
f1674e8
c634f79
71fcfab
1a60ee8
db6774d
28c3de3
5b19055
7513577
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -339,16 +339,19 @@ def run_forever(self): | |
self._check_closed() | ||
if self.is_running(): | ||
raise RuntimeError('Event loop is running.') | ||
policy = events.get_event_loop_policy() | ||
self._set_coroutine_wrapper(self._debug) | ||
self._thread_id = threading.get_ident() | ||
try: | ||
policy.set_running_loop(self) | ||
while True: | ||
self._run_once() | ||
if self._stopping: | ||
break | ||
finally: | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I've left a comment somewhere in this PR on how to merge two try-finally blocks into one. Please do that. |
||
self._stopping = False | ||
self._thread_id = None | ||
policy.set_running_loop(None) | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think we should call |
||
self._set_coroutine_wrapper(False) | ||
|
||
def run_until_complete(self, future): | ||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -514,21 +514,44 @@ class AbstractEventLoopPolicy: | |
def get_event_loop(self): | ||
"""Get the event loop for the current context. | ||
|
||
Returns an event loop object implementing the BaseEventLoop interface, | ||
or raises an exception in case no event loop has been set for the | ||
current context and the current policy does not specify to create one. | ||
Returns an event loop object implementing the BaseEventLoop interface: | ||
- the running loop if it has been set (using set_running_loop) | ||
- the loop for the current context otherwise. | ||
|
||
It should never return None.""" | ||
It may also raise an exception in case no event loop has been set for | ||
the current context and the current policy does not specify to create | ||
one. It should never return None. | ||
""" | ||
raise NotImplementedError | ||
|
||
def set_event_loop(self, loop): | ||
"""Set the event loop for the current context to loop.""" | ||
"""Set the event loop for the current context.""" | ||
raise NotImplementedError | ||
|
||
def get_running_loop(self): | ||
"""Get the running event loop running for the current context, if any. | ||
|
||
Returns an event loop object implementing the BaseEventLoop interface. | ||
If no running loop is set, it returns None. | ||
""" | ||
raise NotImplementedError | ||
|
||
def set_running_loop(self, loop): | ||
"""Set the running event loop for the current context. | ||
|
||
The loop argument can be None to clear the former running loop. | ||
This method should be called by the event loop itself to set the | ||
running loop when it starts, and clear it when it's done. | ||
""" | ||
raise NotImplementedError | ||
|
||
def new_event_loop(self): | ||
"""Create and return a new event loop object according to this | ||
policy's rules. If there's need to set this loop as the event loop for | ||
the current context, set_event_loop must be called explicitly.""" | ||
"""Create and return a new event loop object. | ||
|
||
The loop is created according to the policy's rules. | ||
If there is need to set this loop as the event loop for the | ||
current context, set_event_loop must be called explicitly. | ||
""" | ||
raise NotImplementedError | ||
|
||
# Child processes handling (Unix only). | ||
|
@@ -559,16 +582,22 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy): | |
|
||
class _Local(threading.local): | ||
_loop = None | ||
_running_loop = None | ||
_set_called = False | ||
|
||
def __init__(self): | ||
self._local = self._Local() | ||
|
||
def get_event_loop(self): | ||
"""Get the event loop. | ||
"""Get the event loop for the current context. | ||
|
||
This may be None or an instance of EventLoop. | ||
Returns an event loop object implementing the BaseEventLoop interface: | ||
- the running loop if it has been set (using set_running_loop) | ||
- the loop for the current thread otherwise. | ||
""" | ||
running_loop = self.get_running_loop() | ||
if running_loop is not None: | ||
return running_loop | ||
if (self._local._loop is None and | ||
not self._local._set_called and | ||
isinstance(threading.current_thread(), threading._MainThread)): | ||
|
@@ -579,11 +608,26 @@ def get_event_loop(self): | |
return self._local._loop | ||
|
||
def set_event_loop(self, loop): | ||
"""Set the event loop.""" | ||
"""Set the event loop for the current thread.""" | ||
self._local._set_called = True | ||
assert loop is None or isinstance(loop, AbstractEventLoop) | ||
self._local._loop = loop | ||
|
||
def get_running_loop(self): | ||
"""Get the running event loop for the current thread if any. | ||
|
||
This may be None or an instance of EventLoop. | ||
""" | ||
return self._local._running_loop | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Do we want to return There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I think policy.set_running_loop(None) # Clear the running loop
assert policy.get_running_loop() == None # This test is expected to pass There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Agree, let's keep the current behaviour (returning |
||
|
||
def set_running_loop(self, loop): | ||
"""Set the running event loop for the current thread.""" | ||
assert loop is None or isinstance(loop, AbstractEventLoop) | ||
running_loop = self._local._running_loop | ||
if running_loop is not None and loop is not None: | ||
raise RuntimeError('A loop is already running') | ||
self._local._running_loop = loop | ||
|
||
def new_event_loop(self): | ||
"""Create a new 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.
This has to be outside of the
try
statement.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.
If we do that, this test (not commited yet) would fail:
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 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:
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@gvanrossum
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
andpolicy.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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Without
set_running_loop
andget_running_loop
how will the policy know precisely what's going on? All I'm saying is thatget_event_loop
andset_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.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.