Skip to content

Commit a70232f

Browse files
authored
bpo-32296: Implement asyncio.get_event_loop and _get_running_loop in C. (#4827)
asyncio.get_event_loop(), and, subsequently asyncio._get_running_loop() are one of the most frequently executed functions in asyncio. They also can't be sped up by third-party event loops like uvloop. When implemented in C they become 4x faster.
1 parent d5dda98 commit a70232f

File tree

5 files changed

+465
-28
lines changed

5 files changed

+465
-28
lines changed

Lib/asyncio/events.py

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -652,6 +652,7 @@ def get_running_loop():
652652
653653
This function is thread-specific.
654654
"""
655+
# NOTE: this function is implemented in C (see _asynciomodule.c)
655656
loop = _get_running_loop()
656657
if loop is None:
657658
raise RuntimeError('no running event loop')
@@ -664,6 +665,7 @@ def _get_running_loop():
664665
This is a low-level function intended to be used by event loops.
665666
This function is thread-specific.
666667
"""
668+
# NOTE: this function is implemented in C (see _asynciomodule.c)
667669
running_loop, pid = _running_loop.loop_pid
668670
if running_loop is not None and pid == os.getpid():
669671
return running_loop
@@ -675,6 +677,7 @@ def _set_running_loop(loop):
675677
This is a low-level function intended to be used by event loops.
676678
This function is thread-specific.
677679
"""
680+
# NOTE: this function is implemented in C (see _asynciomodule.c)
678681
_running_loop.loop_pid = (loop, os.getpid())
679682

680683

@@ -711,6 +714,7 @@ def get_event_loop():
711714
If there is no running event loop set, the function will return
712715
the result of `get_event_loop_policy().get_event_loop()` call.
713716
"""
717+
# NOTE: this function is implemented in C (see _asynciomodule.c)
714718
current_loop = _get_running_loop()
715719
if current_loop is not None:
716720
return current_loop
@@ -736,3 +740,26 @@ def set_child_watcher(watcher):
736740
"""Equivalent to calling
737741
get_event_loop_policy().set_child_watcher(watcher)."""
738742
return get_event_loop_policy().set_child_watcher(watcher)
743+
744+
745+
# Alias pure-Python implementations for testing purposes.
746+
_py__get_running_loop = _get_running_loop
747+
_py__set_running_loop = _set_running_loop
748+
_py_get_running_loop = get_running_loop
749+
_py_get_event_loop = get_event_loop
750+
751+
752+
try:
753+
# get_event_loop() is one of the most frequently called
754+
# functions in asyncio. Pure Python implementation is
755+
# about 4 times slower than C-accelerated.
756+
from _asyncio import (_get_running_loop, _set_running_loop,
757+
get_running_loop, get_event_loop)
758+
except ImportError:
759+
pass
760+
else:
761+
# Alias C implementations for testing purposes.
762+
_c__get_running_loop = _get_running_loop
763+
_c__set_running_loop = _set_running_loop
764+
_c_get_running_loop = get_running_loop
765+
_c_get_event_loop = get_event_loop

Lib/test/test_asyncio/test_events.py

Lines changed: 113 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727

2828
import asyncio
2929
from asyncio import coroutines
30+
from asyncio import events
3031
from asyncio import proactor_events
3132
from asyncio import selector_events
3233
from test.test_asyncio import utils as test_utils
@@ -2145,23 +2146,6 @@ def tearDown(self):
21452146
asyncio.set_child_watcher(None)
21462147
super().tearDown()
21472148

2148-
def test_get_event_loop_new_process(self):
2149-
# Issue bpo-32126: The multiprocessing module used by
2150-
# ProcessPoolExecutor is not functional when the
2151-
# multiprocessing.synchronize module cannot be imported.
2152-
support.import_module('multiprocessing.synchronize')
2153-
async def main():
2154-
pool = concurrent.futures.ProcessPoolExecutor()
2155-
result = await self.loop.run_in_executor(
2156-
pool, _test_get_event_loop_new_process__sub_proc)
2157-
pool.shutdown()
2158-
return result
2159-
2160-
self.unpatch_get_running_loop()
2161-
2162-
self.assertEqual(
2163-
self.loop.run_until_complete(main()),
2164-
'hello')
21652149

21662150
if hasattr(selectors, 'KqueueSelector'):
21672151
class KqueueEventLoopTests(UnixEventLoopTestsMixin,
@@ -2722,17 +2706,95 @@ def test_set_event_loop_policy(self):
27222706
self.assertIs(policy, asyncio.get_event_loop_policy())
27232707
self.assertIsNot(policy, old_policy)
27242708

2709+
2710+
class GetEventLoopTestsMixin:
2711+
2712+
_get_running_loop_impl = None
2713+
_set_running_loop_impl = None
2714+
get_running_loop_impl = None
2715+
get_event_loop_impl = None
2716+
2717+
def setUp(self):
2718+
self._get_running_loop_saved = events._get_running_loop
2719+
self._set_running_loop_saved = events._set_running_loop
2720+
self.get_running_loop_saved = events.get_running_loop
2721+
self.get_event_loop_saved = events.get_event_loop
2722+
2723+
events._get_running_loop = type(self)._get_running_loop_impl
2724+
events._set_running_loop = type(self)._set_running_loop_impl
2725+
events.get_running_loop = type(self).get_running_loop_impl
2726+
events.get_event_loop = type(self).get_event_loop_impl
2727+
2728+
asyncio._get_running_loop = type(self)._get_running_loop_impl
2729+
asyncio._set_running_loop = type(self)._set_running_loop_impl
2730+
asyncio.get_running_loop = type(self).get_running_loop_impl
2731+
asyncio.get_event_loop = type(self).get_event_loop_impl
2732+
2733+
super().setUp()
2734+
2735+
self.loop = asyncio.new_event_loop()
2736+
asyncio.set_event_loop(self.loop)
2737+
2738+
watcher = asyncio.SafeChildWatcher()
2739+
watcher.attach_loop(self.loop)
2740+
asyncio.set_child_watcher(watcher)
2741+
2742+
def tearDown(self):
2743+
try:
2744+
asyncio.set_child_watcher(None)
2745+
super().tearDown()
2746+
finally:
2747+
self.loop.close()
2748+
asyncio.set_event_loop(None)
2749+
2750+
events._get_running_loop = self._get_running_loop_saved
2751+
events._set_running_loop = self._set_running_loop_saved
2752+
events.get_running_loop = self.get_running_loop_saved
2753+
events.get_event_loop = self.get_event_loop_saved
2754+
2755+
asyncio._get_running_loop = self._get_running_loop_saved
2756+
asyncio._set_running_loop = self._set_running_loop_saved
2757+
asyncio.get_running_loop = self.get_running_loop_saved
2758+
asyncio.get_event_loop = self.get_event_loop_saved
2759+
2760+
if sys.platform != 'win32':
2761+
2762+
def test_get_event_loop_new_process(self):
2763+
# Issue bpo-32126: The multiprocessing module used by
2764+
# ProcessPoolExecutor is not functional when the
2765+
# multiprocessing.synchronize module cannot be imported.
2766+
support.import_module('multiprocessing.synchronize')
2767+
2768+
async def main():
2769+
pool = concurrent.futures.ProcessPoolExecutor()
2770+
result = await self.loop.run_in_executor(
2771+
pool, _test_get_event_loop_new_process__sub_proc)
2772+
pool.shutdown()
2773+
return result
2774+
2775+
self.assertEqual(
2776+
self.loop.run_until_complete(main()),
2777+
'hello')
2778+
27252779
def test_get_event_loop_returns_running_loop(self):
2780+
class TestError(Exception):
2781+
pass
2782+
27262783
class Policy(asyncio.DefaultEventLoopPolicy):
27272784
def get_event_loop(self):
2728-
raise NotImplementedError
2729-
2730-
loop = None
2785+
raise TestError
27312786

27322787
old_policy = asyncio.get_event_loop_policy()
27332788
try:
27342789
asyncio.set_event_loop_policy(Policy())
27352790
loop = asyncio.new_event_loop()
2791+
2792+
with self.assertRaises(TestError):
2793+
asyncio.get_event_loop()
2794+
asyncio.set_event_loop(None)
2795+
with self.assertRaises(TestError):
2796+
asyncio.get_event_loop()
2797+
27362798
with self.assertRaisesRegex(RuntimeError, 'no running'):
27372799
self.assertIs(asyncio.get_running_loop(), None)
27382800
self.assertIs(asyncio._get_running_loop(), None)
@@ -2743,6 +2805,15 @@ async def func():
27432805
self.assertIs(asyncio._get_running_loop(), loop)
27442806

27452807
loop.run_until_complete(func())
2808+
2809+
asyncio.set_event_loop(loop)
2810+
with self.assertRaises(TestError):
2811+
asyncio.get_event_loop()
2812+
2813+
asyncio.set_event_loop(None)
2814+
with self.assertRaises(TestError):
2815+
asyncio.get_event_loop()
2816+
27462817
finally:
27472818
asyncio.set_event_loop_policy(old_policy)
27482819
if loop is not None:
@@ -2754,5 +2825,27 @@ async def func():
27542825
self.assertIs(asyncio._get_running_loop(), None)
27552826

27562827

2828+
class TestPyGetEventLoop(GetEventLoopTestsMixin, unittest.TestCase):
2829+
2830+
_get_running_loop_impl = events._py__get_running_loop
2831+
_set_running_loop_impl = events._py__set_running_loop
2832+
get_running_loop_impl = events._py_get_running_loop
2833+
get_event_loop_impl = events._py_get_event_loop
2834+
2835+
2836+
try:
2837+
import _asyncio # NoQA
2838+
except ImportError:
2839+
pass
2840+
else:
2841+
2842+
class TestCGetEventLoop(GetEventLoopTestsMixin, unittest.TestCase):
2843+
2844+
_get_running_loop_impl = events._c__get_running_loop
2845+
_set_running_loop_impl = events._c__set_running_loop
2846+
get_running_loop_impl = events._c_get_running_loop
2847+
get_event_loop_impl = events._c_get_event_loop
2848+
2849+
27572850
if __name__ == '__main__':
27582851
unittest.main()
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Implement asyncio._get_running_loop() and get_event_loop() in C. This makes
2+
them 4x faster.

0 commit comments

Comments
 (0)