Skip to content
This repository was archived by the owner on Nov 23, 2017. It is now read-only.
Closed
Show file tree
Hide file tree
Changes from 1 commit
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Prev Previous commit
Next Next commit
Remove running loop context and event loop policy warnings
  • Loading branch information
Vincent Michel
Vincent Michel committed Jun 3, 2016
commit 6e8eaa23d4459a486aeb8c3b963214ac50c0b585
6 changes: 5 additions & 1 deletion asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -342,11 +342,15 @@ def run_forever(self):
self._set_coroutine_wrapper(self._debug)
self._thread_id = threading.get_ident()
try:
with self._running_context():
policy = events.get_event_loop_policy()
policy.set_running_loop(self)

Copy link
Copy Markdown
Member

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.

Copy link
Copy Markdown
Author

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:

    def test_run_loop_inside_loop(self):

        @asyncio.coroutine
        def coro():
            loop2 = asyncio.new_event_loop()
            self.assertRaises(RuntimeError, loop2.run_forever)
            self.assertFalse(loop2.is_running())  # AssertionError: True is not false
            loop2.close()

        loop = asyncio.new_event_loop()
        loop.run_until_complete(coro())
        loop.close()

Copy link
Copy Markdown
Member

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:

  • 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).

@vxgmichel vxgmichel Jun 6, 2016 •

Copy link
Copy Markdown
Author

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

@gvanrossum

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

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.

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

try:
while True:
self._run_once()
if self._stopping:
break
finally:
policy.set_running_loop(None)
finally:

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The 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
Expand Down
35 changes: 0 additions & 35 deletions asyncio/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -16,8 +16,6 @@
import sys
import threading
import traceback
import warnings
import contextlib

from asyncio import compat

Expand Down Expand Up @@ -509,11 +507,6 @@ def get_debug(self):
def set_debug(self, enabled):
raise NotImplementedError

# Running context

def _running_context(self):
return get_event_loop_policy().running_loop_context(self)


class AbstractEventLoopPolicy:
"""Abstract policy for accessing the event loop."""
Expand Down Expand Up @@ -586,18 +579,6 @@ def set_event_loop(self, loop):
raise RuntimeError('The former loop is currently running')
self.set_default_loop(loop)

@contextlib.contextmanager
def running_loop_context(self, loop):
"""Convenience context to set and clear the given loop as running
loop. It is meant to be used inside the loop run methods.
"""
assert isinstance(loop, AbstractEventLoop)
self.set_running_loop(loop)
try:
yield
finally:
self.set_running_loop(None)


class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
"""Default policy implementation for accessing the event loop.
Expand All @@ -613,7 +594,6 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
"""

_loop_factory = None
_warnings = True

class _Local(threading.local):
_loop = None
Expand All @@ -623,11 +603,6 @@ class _Local(threading.local):
def __init__(self):
self._local = self._Local()

def warn(self, *args):
if self._warnings:
raise RuntimeError(*args)
warnings.warn(*args)

def get_default_loop(self):
"""Get the default event loop for the current thread.

Expand Down Expand Up @@ -662,16 +637,6 @@ def set_running_loop(self, loop):
running_loop = self._local._running_loop
if running_loop is not None and loop is not None:
raise RuntimeError('A loop is already running')
# Warnings
if loop is not None:
if default_loop is None:
self.warn(
'Running a loop with no default loop set',
RuntimeWarning)
elif loop != default_loop:
self.warn(
'Running a loop different from the default loop',
RuntimeWarning)
self._local._running_loop = loop

def new_event_loop(self):
Expand Down