Skip to content

Commit fd38a2f

Browse files
gh-93453: No longer create an event loop in get_event_loop() (#98440)
asyncio.get_event_loop() now always return either running event loop or the result of get_event_loop_policy().get_event_loop() call. The latter should now raise an RuntimeError if no current event loop was set instead of creating and setting a new event loop. It affects also a number of asyncio functions and constructors which call get_event_loop() implicitly: ensure_future(), shield(), gather(), etc. DeprecationWarning is no longer emitted if there is no running event loop but the current event loop was set. Co-authored-by: Łukasz Langa <[email protected]>
1 parent b72014c commit fd38a2f

18 files changed

+114
-251
lines changed

Doc/library/asyncio-eventloop.rst

+11-9
Original file line numberDiff line numberDiff line change
@@ -43,10 +43,12 @@ an event loop:
4343

4444
Get the current event loop.
4545

46-
If there is no current event loop set in the current OS thread,
47-
the OS thread is main, and :func:`set_event_loop` has not yet
48-
been called, asyncio will create a new event loop and set it as the
49-
current one.
46+
When called from a coroutine or a callback (e.g. scheduled with
47+
call_soon or similar API), this function will always return the
48+
running event loop.
49+
50+
If there is no running event loop set, the function will return
51+
the result of calling ``get_event_loop_policy().get_event_loop()``.
5052

5153
Because this function has rather complex behavior (especially
5254
when custom event loop policies are in use), using the
@@ -57,11 +59,11 @@ an event loop:
5759
instead of using these lower level functions to manually create and close an
5860
event loop.
5961

60-
.. deprecated:: 3.10
61-
Emits a deprecation warning if there is no running event loop.
62-
In future Python releases, this function may become an alias of
63-
:func:`get_running_loop` and will accordingly raise a
64-
:exc:`RuntimeError` if there is no running event loop.
62+
.. note::
63+
In Python versions 3.10.0--3.10.8 and 3.11.0 this function
64+
(and other functions which used it implicitly) emitted a
65+
:exc:`DeprecationWarning` if there was no running event loop, even if
66+
the current loop was set.
6567

6668
.. function:: set_event_loop(loop)
6769

Doc/library/asyncio-llapi-index.rst

+1-1
Original file line numberDiff line numberDiff line change
@@ -19,7 +19,7 @@ Obtaining the Event Loop
1919
- The **preferred** function to get the running event loop.
2020

2121
* - :func:`asyncio.get_event_loop`
22-
- Get an event loop instance (current or via the policy).
22+
- Get an event loop instance (running or current via the current policy).
2323

2424
* - :func:`asyncio.set_event_loop`
2525
- Set the event loop as current via the current policy.

Doc/library/asyncio-policy.rst

+4
Original file line numberDiff line numberDiff line change
@@ -116,6 +116,10 @@ asyncio ships with the following built-in policies:
116116

117117
On Windows, :class:`ProactorEventLoop` is now used by default.
118118

119+
.. versionchanged:: 3.12
120+
:meth:`get_event_loop` now raises a :exc:`RuntimeError` if there is no
121+
current event loop set.
122+
119123

120124
.. class:: WindowsSelectorEventLoopPolicy
121125

Doc/whatsnew/3.12.rst

+12
Original file line numberDiff line numberDiff line change
@@ -686,6 +686,18 @@ Changes in the Python API
686686
around process-global resources, which are best managed from the main interpreter.
687687
(Contributed by Dong-hee Na in :gh:`99127`.)
688688

689+
* :func:`asyncio.get_event_loop` and many other :mod:`asyncio` functions like
690+
:func:`~asyncio.ensure_future`, :func:`~asyncio.shield` or
691+
:func:`~asyncio.gather`, and also the
692+
:meth:`~asyncio.BaseDefaultEventLoopPolicy.get_event_loop` method of
693+
:class:`~asyncio.BaseDefaultEventLoopPolicy` now raise a :exc:`RuntimeError`
694+
if called when there is no running event loop and the current event loop was
695+
not set.
696+
Previously they implicitly created and set a new current event loop.
697+
:exc:`DeprecationWarning` is no longer emitted if there is no running
698+
event loop but the current event loop is set in the policy.
699+
(Contributed by Serhiy Storchaka in :gh:`93453`.)
700+
689701

690702
Build Changes
691703
=============

Lib/asyncio/events.py

+2-16
Original file line numberDiff line numberDiff line change
@@ -619,7 +619,7 @@ def get_event_loop(self):
619619
620620
Returns an event loop object implementing the BaseEventLoop interface,
621621
or raises an exception in case no event loop has been set for the
622-
current context and the current policy does not specify to create one.
622+
current context.
623623
624624
It should never return None."""
625625
raise NotImplementedError
@@ -672,11 +672,6 @@ def get_event_loop(self):
672672
673673
Returns an instance of EventLoop or raises an exception.
674674
"""
675-
if (self._local._loop is None and
676-
not self._local._set_called and
677-
threading.current_thread() is threading.main_thread()):
678-
self.set_event_loop(self.new_event_loop())
679-
680675
if self._local._loop is None:
681676
raise RuntimeError('There is no current event loop in thread %r.'
682677
% threading.current_thread().name)
@@ -786,16 +781,9 @@ def get_event_loop():
786781
the result of `get_event_loop_policy().get_event_loop()` call.
787782
"""
788783
# NOTE: this function is implemented in C (see _asynciomodule.c)
789-
return _py__get_event_loop()
790-
791-
792-
def _get_event_loop(stacklevel=3):
793784
current_loop = _get_running_loop()
794785
if current_loop is not None:
795786
return current_loop
796-
import warnings
797-
warnings.warn('There is no current event loop',
798-
DeprecationWarning, stacklevel=stacklevel)
799787
return get_event_loop_policy().get_event_loop()
800788

801789

@@ -825,15 +813,14 @@ def set_child_watcher(watcher):
825813
_py__set_running_loop = _set_running_loop
826814
_py_get_running_loop = get_running_loop
827815
_py_get_event_loop = get_event_loop
828-
_py__get_event_loop = _get_event_loop
829816

830817

831818
try:
832819
# get_event_loop() is one of the most frequently called
833820
# functions in asyncio. Pure Python implementation is
834821
# about 4 times slower than C-accelerated.
835822
from _asyncio import (_get_running_loop, _set_running_loop,
836-
get_running_loop, get_event_loop, _get_event_loop)
823+
get_running_loop, get_event_loop)
837824
except ImportError:
838825
pass
839826
else:
@@ -842,7 +829,6 @@ def set_child_watcher(watcher):
842829
_c__set_running_loop = _set_running_loop
843830
_c_get_running_loop = get_running_loop
844831
_c_get_event_loop = get_event_loop
845-
_c__get_event_loop = _get_event_loop
846832

847833

848834
if hasattr(os, 'fork'):

Lib/asyncio/futures.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ def __init__(self, *, loop=None):
7777
the default event loop.
7878
"""
7979
if loop is None:
80-
self._loop = events._get_event_loop()
80+
self._loop = events.get_event_loop()
8181
else:
8282
self._loop = loop
8383
self._callbacks = []
@@ -413,7 +413,7 @@ def wrap_future(future, *, loop=None):
413413
assert isinstance(future, concurrent.futures.Future), \
414414
f'concurrent.futures.Future is expected, got {future!r}'
415415
if loop is None:
416-
loop = events._get_event_loop()
416+
loop = events.get_event_loop()
417417
new_future = loop.create_future()
418418
_chain_future(future, new_future)
419419
return new_future

Lib/asyncio/streams.py

+2-2
Original file line numberDiff line numberDiff line change
@@ -125,7 +125,7 @@ class FlowControlMixin(protocols.Protocol):
125125

126126
def __init__(self, loop=None):
127127
if loop is None:
128-
self._loop = events._get_event_loop(stacklevel=4)
128+
self._loop = events.get_event_loop()
129129
else:
130130
self._loop = loop
131131
self._paused = False
@@ -404,7 +404,7 @@ def __init__(self, limit=_DEFAULT_LIMIT, loop=None):
404404

405405
self._limit = limit
406406
if loop is None:
407-
self._loop = events._get_event_loop()
407+
self._loop = events.get_event_loop()
408408
else:
409409
self._loop = loop
410410
self._buffer = bytearray()

Lib/asyncio/tasks.py

+3-3
Original file line numberDiff line numberDiff line change
@@ -582,7 +582,7 @@ def as_completed(fs, *, timeout=None):
582582
from .queues import Queue # Import here to avoid circular import problem.
583583
done = Queue()
584584

585-
loop = events._get_event_loop()
585+
loop = events.get_event_loop()
586586
todo = {ensure_future(f, loop=loop) for f in set(fs)}
587587
timeout_handle = None
588588

@@ -668,7 +668,7 @@ def _ensure_future(coro_or_future, *, loop=None):
668668
'is required')
669669

670670
if loop is None:
671-
loop = events._get_event_loop(stacklevel=4)
671+
loop = events.get_event_loop()
672672
try:
673673
return loop.create_task(coro_or_future)
674674
except RuntimeError:
@@ -749,7 +749,7 @@ def gather(*coros_or_futures, return_exceptions=False):
749749
gather won't cancel any other awaitables.
750750
"""
751751
if not coros_or_futures:
752-
loop = events._get_event_loop()
752+
loop = events.get_event_loop()
753753
outer = loop.create_future()
754754
outer.set_result([])
755755
return outer

Lib/test/test_asyncio/test_base_events.py

+1-1
Original file line numberDiff line numberDiff line change
@@ -746,7 +746,7 @@ async def coro():
746746
def test_env_var_debug(self):
747747
code = '\n'.join((
748748
'import asyncio',
749-
'loop = asyncio.get_event_loop()',
749+
'loop = asyncio.new_event_loop()',
750750
'print(loop.get_debug())'))
751751

752752
# Test with -E to not fail if the unit test was run with

Lib/test/test_asyncio/test_events.py

+19-56
Original file line numberDiff line numberDiff line change
@@ -2550,29 +2550,8 @@ def test_event_loop_policy(self):
25502550
def test_get_event_loop(self):
25512551
policy = asyncio.DefaultEventLoopPolicy()
25522552
self.assertIsNone(policy._local._loop)
2553-
2554-
loop = policy.get_event_loop()
2555-
self.assertIsInstance(loop, asyncio.AbstractEventLoop)
2556-
2557-
self.assertIs(policy._local._loop, loop)
2558-
self.assertIs(loop, policy.get_event_loop())
2559-
loop.close()
2560-
2561-
def test_get_event_loop_calls_set_event_loop(self):
2562-
policy = asyncio.DefaultEventLoopPolicy()
2563-
2564-
with mock.patch.object(
2565-
policy, "set_event_loop",
2566-
wraps=policy.set_event_loop) as m_set_event_loop:
2567-
2568-
loop = policy.get_event_loop()
2569-
2570-
# policy._local._loop must be set through .set_event_loop()
2571-
# (the unix DefaultEventLoopPolicy needs this call to attach
2572-
# the child watcher correctly)
2573-
m_set_event_loop.assert_called_with(loop)
2574-
2575-
loop.close()
2553+
with self.assertRaisesRegex(RuntimeError, 'no current event loop'):
2554+
policy.get_event_loop()
25762555

25772556
def test_get_event_loop_after_set_none(self):
25782557
policy = asyncio.DefaultEventLoopPolicy()
@@ -2599,7 +2578,8 @@ def test_new_event_loop(self):
25992578

26002579
def test_set_event_loop(self):
26012580
policy = asyncio.DefaultEventLoopPolicy()
2602-
old_loop = policy.get_event_loop()
2581+
old_loop = policy.new_event_loop()
2582+
policy.set_event_loop(old_loop)
26032583

26042584
self.assertRaises(TypeError, policy.set_event_loop, object())
26052585

@@ -2716,15 +2696,11 @@ def get_event_loop(self):
27162696
asyncio.set_event_loop_policy(Policy())
27172697
loop = asyncio.new_event_loop()
27182698

2719-
with self.assertWarns(DeprecationWarning) as cm:
2720-
with self.assertRaises(TestError):
2721-
asyncio.get_event_loop()
2722-
self.assertEqual(cm.filename, __file__)
2699+
with self.assertRaises(TestError):
2700+
asyncio.get_event_loop()
27232701
asyncio.set_event_loop(None)
2724-
with self.assertWarns(DeprecationWarning) as cm:
2725-
with self.assertRaises(TestError):
2726-
asyncio.get_event_loop()
2727-
self.assertEqual(cm.filename, __file__)
2702+
with self.assertRaises(TestError):
2703+
asyncio.get_event_loop()
27282704

27292705
with self.assertRaisesRegex(RuntimeError, 'no running'):
27302706
asyncio.get_running_loop()
@@ -2738,16 +2714,11 @@ async def func():
27382714
loop.run_until_complete(func())
27392715

27402716
asyncio.set_event_loop(loop)
2741-
with self.assertWarns(DeprecationWarning) as cm:
2742-
with self.assertRaises(TestError):
2743-
asyncio.get_event_loop()
2744-
self.assertEqual(cm.filename, __file__)
2745-
2717+
with self.assertRaises(TestError):
2718+
asyncio.get_event_loop()
27462719
asyncio.set_event_loop(None)
2747-
with self.assertWarns(DeprecationWarning) as cm:
2748-
with self.assertRaises(TestError):
2749-
asyncio.get_event_loop()
2750-
self.assertEqual(cm.filename, __file__)
2720+
with self.assertRaises(TestError):
2721+
asyncio.get_event_loop()
27512722

27522723
finally:
27532724
asyncio.set_event_loop_policy(old_policy)
@@ -2766,15 +2737,11 @@ def test_get_event_loop_returns_running_loop2(self):
27662737
loop = asyncio.new_event_loop()
27672738
self.addCleanup(loop.close)
27682739

2769-
with self.assertWarns(DeprecationWarning) as cm:
2770-
loop2 = asyncio.get_event_loop()
2771-
self.addCleanup(loop2.close)
2772-
self.assertEqual(cm.filename, __file__)
2740+
with self.assertRaisesRegex(RuntimeError, 'no current'):
2741+
asyncio.get_event_loop()
27732742
asyncio.set_event_loop(None)
2774-
with self.assertWarns(DeprecationWarning) as cm:
2775-
with self.assertRaisesRegex(RuntimeError, 'no current'):
2776-
asyncio.get_event_loop()
2777-
self.assertEqual(cm.filename, __file__)
2743+
with self.assertRaisesRegex(RuntimeError, 'no current'):
2744+
asyncio.get_event_loop()
27782745

27792746
with self.assertRaisesRegex(RuntimeError, 'no running'):
27802747
asyncio.get_running_loop()
@@ -2788,15 +2755,11 @@ async def func():
27882755
loop.run_until_complete(func())
27892756

27902757
asyncio.set_event_loop(loop)
2791-
with self.assertWarns(DeprecationWarning) as cm:
2792-
self.assertIs(asyncio.get_event_loop(), loop)
2793-
self.assertEqual(cm.filename, __file__)
2758+
self.assertIs(asyncio.get_event_loop(), loop)
27942759

27952760
asyncio.set_event_loop(None)
2796-
with self.assertWarns(DeprecationWarning) as cm:
2797-
with self.assertRaisesRegex(RuntimeError, 'no current'):
2798-
asyncio.get_event_loop()
2799-
self.assertEqual(cm.filename, __file__)
2761+
with self.assertRaisesRegex(RuntimeError, 'no current'):
2762+
asyncio.get_event_loop()
28002763

28012764
finally:
28022765
asyncio.set_event_loop_policy(old_policy)

Lib/test/test_asyncio/test_futures.py

+8-16
Original file line numberDiff line numberDiff line change
@@ -146,10 +146,8 @@ def test_initial_state(self):
146146
self.assertTrue(f.cancelled())
147147

148148
def test_constructor_without_loop(self):
149-
with self.assertWarns(DeprecationWarning) as cm:
150-
with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'):
151-
self._new_future()
152-
self.assertEqual(cm.filename, __file__)
149+
with self.assertRaisesRegex(RuntimeError, 'no current event loop'):
150+
self._new_future()
153151

154152
def test_constructor_use_running_loop(self):
155153
async def test():
@@ -159,12 +157,10 @@ async def test():
159157
self.assertIs(f.get_loop(), self.loop)
160158

161159
def test_constructor_use_global_loop(self):
162-
# Deprecated in 3.10
160+
# Deprecated in 3.10, undeprecated in 3.12
163161
asyncio.set_event_loop(self.loop)
164162
self.addCleanup(asyncio.set_event_loop, None)
165-
with self.assertWarns(DeprecationWarning) as cm:
166-
f = self._new_future()
167-
self.assertEqual(cm.filename, __file__)
163+
f = self._new_future()
168164
self.assertIs(f._loop, self.loop)
169165
self.assertIs(f.get_loop(), self.loop)
170166

@@ -500,10 +496,8 @@ def run(arg):
500496
return (arg, threading.get_ident())
501497
ex = concurrent.futures.ThreadPoolExecutor(1)
502498
f1 = ex.submit(run, 'oi')
503-
with self.assertWarns(DeprecationWarning) as cm:
504-
with self.assertRaises(RuntimeError):
505-
asyncio.wrap_future(f1)
506-
self.assertEqual(cm.filename, __file__)
499+
with self.assertRaisesRegex(RuntimeError, 'no current event loop'):
500+
asyncio.wrap_future(f1)
507501
ex.shutdown(wait=True)
508502

509503
def test_wrap_future_use_running_loop(self):
@@ -518,16 +512,14 @@ async def test():
518512
ex.shutdown(wait=True)
519513

520514
def test_wrap_future_use_global_loop(self):
521-
# Deprecated in 3.10
515+
# Deprecated in 3.10, undeprecated in 3.12
522516
asyncio.set_event_loop(self.loop)
523517
self.addCleanup(asyncio.set_event_loop, None)
524518
def run(arg):
525519
return (arg, threading.get_ident())
526520
ex = concurrent.futures.ThreadPoolExecutor(1)
527521
f1 = ex.submit(run, 'oi')
528-
with self.assertWarns(DeprecationWarning) as cm:
529-
f2 = asyncio.wrap_future(f1)
530-
self.assertEqual(cm.filename, __file__)
522+
f2 = asyncio.wrap_future(f1)
531523
self.assertIs(self.loop, f2._loop)
532524
ex.shutdown(wait=True)
533525

0 commit comments

Comments
 (0)