From b30bcdc87cdc8f60c06a3cc2e6f2904c35147d1b Mon Sep 17 00:00:00 2001 From: Vincent Michel Date: Wed, 25 May 2016 17:51:43 +0200 Subject: [PATCH 01/18] Modify the interface for AbstractEventLoopPolicy - 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. --- asyncio/events.py | 112 +++++++++++++++++++++++++++++++++++++++++----- 1 file changed, 101 insertions(+), 11 deletions(-) diff --git a/asyncio/events.py b/asyncio/events.py index c48c5bed..5ced12aa 100644 --- a/asyncio/events.py +++ b/asyncio/events.py @@ -16,6 +16,8 @@ import sys import threading import traceback +import warnings +import contextlib from asyncio import compat @@ -507,28 +509,52 @@ 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.""" - def get_event_loop(self): - """Get the event loop for the current context. + def get_default_loop(self): + """Get the default 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. - It should never return None.""" + It should never return None. + """ + raise NotImplementedError + + def set_default_loop(self, loop): + """Set the default event loop for the current context to loop.""" raise NotImplementedError - def set_event_loop(self, loop): - """Set the event loop for the current context to loop.""" + 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.""" + policy's rules. If there's need to set this loop as the default loop + for the current context, set_event_loop must be called explicitly. + """ raise NotImplementedError # Child processes handling (Unix only). @@ -541,6 +567,37 @@ def set_child_watcher(self, watcher): """Set the watcher for child processes.""" raise NotImplementedError + # Non-abstract methods + + def get_event_loop(self): + """Return the running event loop if any, and the default event + loop otherwise. + """ + running_loop = self.get_running_loop() + if running_loop is not None: + return running_loop + return self.get_default_loop() + + def set_event_loop(self, loop): + """Set the default event loop if the former loop is not currently + running. + """ + if self.get_running_loop() is not None: + 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. @@ -556,16 +613,23 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy): """ _loop_factory = None + _warnings = True 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. + 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. This may be None or an instance of EventLoop. """ @@ -578,12 +642,38 @@ def get_event_loop(self): % threading.current_thread().name) return self._local._loop - def set_event_loop(self, loop): - """Set the event loop.""" + def set_default_loop(self, loop): + """Set the default 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 + + def set_running_loop(self, loop): + """Set the running event loop for the current thread.""" + assert loop is None or isinstance(loop, AbstractEventLoop) + default_loop = self._local._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): """Create a new event loop. From 705a54c6183e4fb36a49e1fd528e12fbcd501301 Mon Sep 17 00:00:00 2001 From: Vincent Michel Date: Wed, 25 May 2016 18:13:04 +0200 Subject: [PATCH 02/18] Update BaseEventLoop.run_forever to set and clear the running loop --- asyncio/base_events.py | 9 +++++---- 1 file changed, 5 insertions(+), 4 deletions(-) diff --git a/asyncio/base_events.py b/asyncio/base_events.py index e5feb998..cdd24ae8 100644 --- a/asyncio/base_events.py +++ b/asyncio/base_events.py @@ -342,10 +342,11 @@ def run_forever(self): self._set_coroutine_wrapper(self._debug) self._thread_id = threading.get_ident() try: - while True: - self._run_once() - if self._stopping: - break + with self._running_context(): + while True: + self._run_once() + if self._stopping: + break finally: self._stopping = False self._thread_id = None From 2e55a602df3cfcce6a3569ab0e2d2c1d2039f7ff Mon Sep 17 00:00:00 2001 From: Vincent Michel Date: Wed, 25 May 2016 18:13:53 +0200 Subject: [PATCH 03/18] Ignore event loop policy warnings during the tests --- asyncio/test_utils.py | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/asyncio/test_utils.py b/asyncio/test_utils.py index 396e6aed..33336daf 100644 --- a/asyncio/test_utils.py +++ b/asyncio/test_utils.py @@ -407,6 +407,8 @@ def set_event_loop(self, loop, *, cleanup=True): assert loop is not None # ensure that the event loop is passed explicitly in asyncio events.set_event_loop(None) + # Disable event loop policy warnings + events.get_event_loop_policy()._warnings = False if cleanup: self.addCleanup(loop.close) @@ -417,6 +419,8 @@ def new_test_loop(self, gen=None): def tearDown(self): events.set_event_loop(None) + # Enable event loop policy warnings + events.get_event_loop_policy()._warnings = True # Detect CPython bug #23353: ensure that yield/yield-from is not used # in an except block of a generator From f117c7571d9739a4a5b500e3026bba769e9fe624 Mon Sep 17 00:00:00 2001 From: Vincent Michel Date: Wed, 25 May 2016 18:14:47 +0200 Subject: [PATCH 04/18] Fix SleepTests and TimeoutTests --- tests/test_tasks.py | 12 ++---------- 1 file changed, 2 insertions(+), 10 deletions(-) diff --git a/tests/test_tasks.py b/tests/test_tasks.py index 128b7ce9..ea537ccb 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -2235,11 +2235,7 @@ def test_run_coroutine_threadsafe_task_factory_exception(self): class SleepTests(test_utils.TestCase): def setUp(self): self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(None) - - def tearDown(self): - self.loop.close() - self.loop = None + self.set_event_loop(self.loop) def test_sleep_zero(self): result = 0 @@ -2263,11 +2259,7 @@ def coro(): class TimeoutTests(test_utils.TestCase): def setUp(self): self.loop = asyncio.new_event_loop() - asyncio.set_event_loop(None) - - def tearDown(self): - self.loop.close() - self.loop = None + self.set_event_loop(self.loop) def test_timeout(self): canceled_raised = [False] From 1c3a166af007bd517b4f09796b97f9f181a11a5d Mon Sep 17 00:00:00 2001 From: Vincent Michel Date: Fri, 3 Jun 2016 17:40:29 +0200 Subject: [PATCH 05/18] Revert "Ignore event loop policy warnings during the tests" This reverts commit 2e55a602df3cfcce6a3569ab0e2d2c1d2039f7ff. --- asyncio/test_utils.py | 4 ---- 1 file changed, 4 deletions(-) diff --git a/asyncio/test_utils.py b/asyncio/test_utils.py index 33336daf..396e6aed 100644 --- a/asyncio/test_utils.py +++ b/asyncio/test_utils.py @@ -407,8 +407,6 @@ def set_event_loop(self, loop, *, cleanup=True): assert loop is not None # ensure that the event loop is passed explicitly in asyncio events.set_event_loop(None) - # Disable event loop policy warnings - events.get_event_loop_policy()._warnings = False if cleanup: self.addCleanup(loop.close) @@ -419,8 +417,6 @@ def new_test_loop(self, gen=None): def tearDown(self): events.set_event_loop(None) - # Enable event loop policy warnings - events.get_event_loop_policy()._warnings = True # Detect CPython bug #23353: ensure that yield/yield-from is not used # in an except block of a generator From 6e8eaa23d4459a486aeb8c3b963214ac50c0b585 Mon Sep 17 00:00:00 2001 From: Vincent Michel Date: Fri, 3 Jun 2016 17:53:57 +0200 Subject: [PATCH 06/18] Remove running loop context and event loop policy warnings --- asyncio/base_events.py | 6 +++++- asyncio/events.py | 35 ----------------------------------- 2 files changed, 5 insertions(+), 36 deletions(-) diff --git a/asyncio/base_events.py b/asyncio/base_events.py index cdd24ae8..c2c7a714 100644 --- a/asyncio/base_events.py +++ b/asyncio/base_events.py @@ -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) + try: while True: self._run_once() if self._stopping: break + finally: + policy.set_running_loop(None) finally: self._stopping = False self._thread_id = None diff --git a/asyncio/events.py b/asyncio/events.py index 5ced12aa..2a53dac8 100644 --- a/asyncio/events.py +++ b/asyncio/events.py @@ -16,8 +16,6 @@ import sys import threading import traceback -import warnings -import contextlib from asyncio import compat @@ -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.""" @@ -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. @@ -613,7 +594,6 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy): """ _loop_factory = None - _warnings = True class _Local(threading.local): _loop = None @@ -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. @@ -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): From be2c0f10a08676078ac7d05a73b13731aaab0d65 Mon Sep 17 00:00:00 2001 From: vinmic Date: Fri, 3 Jun 2016 20:20:56 +0200 Subject: [PATCH 07/18] Revert "Fix SleepTests and TimeoutTests" This reverts commit f117c7571d9739a4a5b500e3026bba769e9fe624. --- tests/test_tasks.py | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/tests/test_tasks.py b/tests/test_tasks.py index ea537ccb..128b7ce9 100644 --- a/tests/test_tasks.py +++ b/tests/test_tasks.py @@ -2235,7 +2235,11 @@ def test_run_coroutine_threadsafe_task_factory_exception(self): class SleepTests(test_utils.TestCase): def setUp(self): self.loop = asyncio.new_event_loop() - self.set_event_loop(self.loop) + asyncio.set_event_loop(None) + + def tearDown(self): + self.loop.close() + self.loop = None def test_sleep_zero(self): result = 0 @@ -2259,7 +2263,11 @@ def coro(): class TimeoutTests(test_utils.TestCase): def setUp(self): self.loop = asyncio.new_event_loop() - self.set_event_loop(self.loop) + asyncio.set_event_loop(None) + + def tearDown(self): + self.loop.close() + self.loop = None def test_timeout(self): canceled_raised = [False] From c1cb666d4ee5cc5be5cd4690e6e1ed5d5dc7e43a Mon Sep 17 00:00:00 2001 From: vinmic Date: Fri, 3 Jun 2016 21:24:06 +0200 Subject: [PATCH 08/18] Update docstrings for AbstractEventLoopPolicy --- asyncio/events.py | 12 +++--------- 1 file changed, 3 insertions(+), 9 deletions(-) diff --git a/asyncio/events.py b/asyncio/events.py index 2a53dac8..65d52b0f 100644 --- a/asyncio/events.py +++ b/asyncio/events.py @@ -523,7 +523,7 @@ def get_default_loop(self): raise NotImplementedError def set_default_loop(self, loop): - """Set the default event loop for the current context to loop.""" + """Set the default event loop for the current context.""" raise NotImplementedError def get_running_loop(self): @@ -563,20 +563,14 @@ def set_child_watcher(self, watcher): # Non-abstract methods def get_event_loop(self): - """Return the running event loop if any, and the default event - loop otherwise. - """ + """Return the running loop if any, and the default loop otherwise.""" running_loop = self.get_running_loop() if running_loop is not None: return running_loop return self.get_default_loop() def set_event_loop(self, loop): - """Set the default event loop if the former loop is not currently - running. - """ - if self.get_running_loop() is not None: - raise RuntimeError('The former loop is currently running') + """Set the default event loop for the current context.""" self.set_default_loop(loop) From 8788c72b0de75a672ccba58fab2ee5e3d198f443 Mon Sep 17 00:00:00 2001 From: vinmic Date: Fri, 3 Jun 2016 23:40:00 +0200 Subject: [PATCH 09/18] Remove get/set_default_loop --- asyncio/events.py | 45 ++++++++++++++++++++------------------------- 1 file changed, 20 insertions(+), 25 deletions(-) diff --git a/asyncio/events.py b/asyncio/events.py index 65d52b0f..7639156a 100644 --- a/asyncio/events.py +++ b/asyncio/events.py @@ -511,19 +511,22 @@ def set_debug(self, enabled): class AbstractEventLoopPolicy: """Abstract policy for accessing the event loop.""" - def get_default_loop(self): - """Get the default event loop for the current context. + def get_event_loop(self): + """Get the event loop for the current context. + + 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. - Returns an event loop object implementing the BaseEventLoop interface, - or raises an exception in case no event loop has been set for the + 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_default_loop(self, loop): - """Set the default event loop for the current context.""" + def set_event_loop(self, loop): + """Set the event loop for the current context.""" raise NotImplementedError def get_running_loop(self): @@ -545,8 +548,8 @@ def set_running_loop(self, loop): 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 default loop - for the current context, set_event_loop must be called explicitly. + 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. """ raise NotImplementedError @@ -560,19 +563,6 @@ def set_child_watcher(self, watcher): """Set the watcher for child processes.""" raise NotImplementedError - # Non-abstract methods - - def get_event_loop(self): - """Return the running loop if any, and the default loop otherwise.""" - running_loop = self.get_running_loop() - if running_loop is not None: - return running_loop - return self.get_default_loop() - - def set_event_loop(self, loop): - """Set the default event loop for the current context.""" - self.set_default_loop(loop) - class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy): """Default policy implementation for accessing the event loop. @@ -597,11 +587,16 @@ class _Local(threading.local): def __init__(self): self._local = self._Local() - def get_default_loop(self): - """Get the default event loop for the current thread. + def get_event_loop(self): + """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)): @@ -611,7 +606,7 @@ def get_default_loop(self): % threading.current_thread().name) return self._local._loop - def set_default_loop(self, loop): + def set_event_loop(self, loop): """Set the default event loop for the current thread.""" self._local._set_called = True assert loop is None or isinstance(loop, AbstractEventLoop) From 121cca3fef3820a1850df91f6d772e40ed5844d4 Mon Sep 17 00:00:00 2001 From: vinmic Date: Fri, 3 Jun 2016 23:41:57 +0200 Subject: [PATCH 10/18] Add disable_get_running_loop method to TestCase --- asyncio/test_utils.py | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/asyncio/test_utils.py b/asyncio/test_utils.py index 396e6aed..e91d8bae 100644 --- a/asyncio/test_utils.py +++ b/asyncio/test_utils.py @@ -403,9 +403,25 @@ def get_function_source(func): class TestCase(unittest.TestCase): + def disable_get_event_loop(self): + policy = events.get_event_loop_policy() + if hasattr(policy, '_patched_get_event_loop'): + return + + def reset_event_loop_method(): + policy.get_running_loop = old_get_running_loop + del policy._patched_get_event_loop + + old_get_running_loop = policy.get_running_loop + policy.get_running_loop = lambda: None + policy._patched_get_event_loop = True + + self.addCleanup(reset_event_loop_method) + def set_event_loop(self, loop, *, cleanup=True): assert loop is not None # ensure that the event loop is passed explicitly in asyncio + self.disable_get_event_loop() events.set_event_loop(None) if cleanup: self.addCleanup(loop.close) From f1674e8d55272095103faedb696d4c29b14ed92c Mon Sep 17 00:00:00 2001 From: vinmic Date: Sat, 4 Jun 2016 00:50:46 +0200 Subject: [PATCH 11/18] Update set_event_loop docstring --- asyncio/events.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/asyncio/events.py b/asyncio/events.py index 7639156a..12bb46c6 100644 --- a/asyncio/events.py +++ b/asyncio/events.py @@ -607,7 +607,7 @@ def get_event_loop(self): return self._local._loop def set_event_loop(self, loop): - """Set the default event loop for the current thread.""" + """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 From c634f7949b9a7f5663c3e0ae95a3b2407811a345 Mon Sep 17 00:00:00 2001 From: vinmic Date: Sat, 4 Jun 2016 00:53:01 +0200 Subject: [PATCH 12/18] Merge both try-finally blocks into one in BaseEventLoop.run_forever --- asyncio/base_events.py | 16 +++++++--------- 1 file changed, 7 insertions(+), 9 deletions(-) diff --git a/asyncio/base_events.py b/asyncio/base_events.py index c2c7a714..8462f303 100644 --- a/asyncio/base_events.py +++ b/asyncio/base_events.py @@ -339,22 +339,20 @@ def run_forever(self): self._check_closed() if self.is_running(): raise RuntimeError('Event loop is running.') + policy = events.get_event_loop_policy() + policy.set_running_loop(self) self._set_coroutine_wrapper(self._debug) self._thread_id = threading.get_ident() try: - policy = events.get_event_loop_policy() - policy.set_running_loop(self) - try: - while True: - self._run_once() - if self._stopping: - break - finally: - policy.set_running_loop(None) + while True: + self._run_once() + if self._stopping: + break finally: self._stopping = False self._thread_id = None self._set_coroutine_wrapper(False) + policy.set_running_loop(None) def run_until_complete(self, future): """Run until the Future is done. From 71fcfabbc6d21bc07cb5ac63188c07d228e66f86 Mon Sep 17 00:00:00 2001 From: vinmic Date: Sat, 4 Jun 2016 12:37:43 +0200 Subject: [PATCH 13/18] Fix new_event_loop docstring for AbstractEventLoopPolicy --- asyncio/events.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/asyncio/events.py b/asyncio/events.py index 12bb46c6..d7cc7b07 100644 --- a/asyncio/events.py +++ b/asyncio/events.py @@ -547,9 +547,11 @@ def set_running_loop(self, loop): 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 From 1a60ee8eb1452ff7a73de7628e5f1c8862c56b38 Mon Sep 17 00:00:00 2001 From: vinmic Date: Sat, 4 Jun 2016 13:06:54 +0200 Subject: [PATCH 14/18] Add test_set_running_loop to PolicyTests --- tests/test_events.py | 24 ++++++++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/tests/test_events.py b/tests/test_events.py index d52213ce..5e11f576 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -2467,6 +2467,9 @@ def test_event_loop_policy(self): self.assertRaises(NotImplementedError, policy.get_event_loop) self.assertRaises(NotImplementedError, policy.set_event_loop, object()) self.assertRaises(NotImplementedError, policy.new_event_loop) + self.assertRaises(NotImplementedError, policy.get_running_loop) + self.assertRaises(NotImplementedError, policy.set_running_loop, + object()) self.assertRaises(NotImplementedError, policy.get_child_watcher) self.assertRaises(NotImplementedError, policy.set_child_watcher, object()) @@ -2534,6 +2537,27 @@ def test_set_event_loop(self): loop.close() old_loop.close() + def test_set_running_loop(self): + policy = asyncio.DefaultEventLoopPolicy() + self.assertIsNone(policy._local._running_loop) + self.assertIsNone(policy.get_running_loop()) + + self.assertRaises(AssertionError, policy.set_running_loop, object()) + + loop = policy.new_event_loop() + policy.set_running_loop(loop) + + self.assertIs(policy._local._running_loop, loop) + self.assertIs(policy.get_running_loop(), loop) + loop.close() + + loop2 = policy.new_event_loop() + self.assertRaises(RuntimeError, policy.set_running_loop, loop2) + loop2.close() + + policy.set_running_loop(None) + self.assertIsNone(policy._local._running_loop) + def test_get_event_loop_policy(self): policy = asyncio.get_event_loop_policy() self.assertIsInstance(policy, asyncio.AbstractEventLoopPolicy) From db6774db69aec75a3dc4a21a75f6a93825270b1d Mon Sep 17 00:00:00 2001 From: vinmic Date: Sat, 4 Jun 2016 13:08:46 +0200 Subject: [PATCH 15/18] Clean set_running_loop --- asyncio/events.py | 1 - 1 file changed, 1 deletion(-) diff --git a/asyncio/events.py b/asyncio/events.py index d7cc7b07..28121193 100644 --- a/asyncio/events.py +++ b/asyncio/events.py @@ -624,7 +624,6 @@ def get_running_loop(self): def set_running_loop(self, loop): """Set the running event loop for the current thread.""" assert loop is None or isinstance(loop, AbstractEventLoop) - default_loop = self._local._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') From 28c3de3d9d8b44cdddcf72378febb52e27042d85 Mon Sep 17 00:00:00 2001 From: vinmic Date: Sat, 4 Jun 2016 13:30:38 +0200 Subject: [PATCH 16/18] Add test_get_event_loop_after_set_running_loop to PolicyTests --- tests/test_events.py | 27 +++++++++++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/tests/test_events.py b/tests/test_events.py index 5e11f576..52525424 100644 --- a/tests/test_events.py +++ b/tests/test_events.py @@ -2558,6 +2558,33 @@ def test_set_running_loop(self): policy.set_running_loop(None) self.assertIsNone(policy._local._running_loop) + def test_get_event_loop_after_set_running_loop(self): + policy = asyncio.DefaultEventLoopPolicy() + + running_loop = policy.new_event_loop() + policy.set_running_loop(running_loop) + + self.assertIsNone(policy._local._loop) + self.assertIs(policy.get_event_loop(), running_loop) + + loop = policy.new_event_loop() + policy.set_event_loop(loop) + + self.assertIs(policy._local._loop, loop) + self.assertIs(policy.get_event_loop(), running_loop) + + policy.set_running_loop(None) + running_loop.close() + + self.assertIs(policy._local._loop, loop) + self.assertIs(policy.get_event_loop(), loop) + + policy.set_event_loop(None) + loop.close() + + self.assertIsNone(policy._local._loop) + self.assertRaises(RuntimeError, policy.get_event_loop) + def test_get_event_loop_policy(self): policy = asyncio.get_event_loop_policy() self.assertIsInstance(policy, asyncio.AbstractEventLoopPolicy) From 5b190555ee1dfaec09d8cd2c60b0ca5613f20a6c Mon Sep 17 00:00:00 2001 From: vinmic Date: Sat, 4 Jun 2016 13:31:15 +0200 Subject: [PATCH 17/18] Fix get_event_loop docstring --- asyncio/events.py | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/asyncio/events.py b/asyncio/events.py index 28121193..b36738bc 100644 --- a/asyncio/events.py +++ b/asyncio/events.py @@ -518,10 +518,9 @@ def get_event_loop(self): - the running loop if it has been set (using set_running_loop) - the loop for the current context otherwise. - 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. + 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 From 751357720fd936607db6b10d7fb765f6a296244d Mon Sep 17 00:00:00 2001 From: vinmic Date: Sat, 4 Jun 2016 13:33:05 +0200 Subject: [PATCH 18/18] Update running context in BaseEventLoop.run_forever --- asyncio/base_events.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/asyncio/base_events.py b/asyncio/base_events.py index 8462f303..c519fda4 100644 --- a/asyncio/base_events.py +++ b/asyncio/base_events.py @@ -340,10 +340,10 @@ def run_forever(self): if self.is_running(): raise RuntimeError('Event loop is running.') policy = events.get_event_loop_policy() - policy.set_running_loop(self) 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: @@ -351,8 +351,8 @@ def run_forever(self): finally: self._stopping = False self._thread_id = None - self._set_coroutine_wrapper(False) policy.set_running_loop(None) + self._set_coroutine_wrapper(False) def run_until_complete(self, future): """Run until the Future is done.