Skip to content

bpo-39529: Deprecate creating new event loop in asyncio.get_event_loop() #23554

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
5 changes: 5 additions & 0 deletions Doc/library/asyncio-eventloop.rst
Original file line number Diff line number Diff line change
Expand Up @@ -53,6 +53,11 @@ an event loop:
Consider also using the :func:`asyncio.run` function instead of using
lower level functions to manually create and close an event loop.

.. deprecated:: 3.10
Deprecation warning is emitted if there is no running event loop.
If future Python releases this function will be an alias of
:func:`get_running_loop`.

.. function:: set_event_loop(loop)

Set *loop* as a current event loop for the current OS thread.
Expand Down
12 changes: 12 additions & 0 deletions Doc/library/asyncio-future.rst
Original file line number Diff line number Diff line change
Expand Up @@ -57,12 +57,20 @@ Future Functions
.. versionchanged:: 3.5.1
The function accepts any :term:`awaitable` object.

.. deprecated:: 3.10
Deprecation warning is emitted if *obj* is not a Future-like object
and *loop* is not specified and there is no running event loop.


.. function:: wrap_future(future, *, loop=None)

Wrap a :class:`concurrent.futures.Future` object in a
:class:`asyncio.Future` object.

.. deprecated:: 3.10
Deprecation warning is emitted if *future* is not a Future-like object
and *loop* is not specified and there is no running event loop.


Future Object
=============
Expand Down Expand Up @@ -90,6 +98,10 @@ Future Object
.. versionchanged:: 3.7
Added support for the :mod:`contextvars` module.

.. deprecated:: 3.10
Deprecation warning is emitted if *loop* is not specified
and there is no running event loop.

.. method:: result()

Return the result of the Future.
Expand Down
17 changes: 17 additions & 0 deletions Doc/library/asyncio-task.rst
Original file line number Diff line number Diff line change
Expand Up @@ -397,6 +397,11 @@ Running Tasks Concurrently
If the *gather* itself is cancelled, the cancellation is
propagated regardless of *return_exceptions*.

.. deprecated:: 3.10
Deprecation warning is emitted if no positional arguments are provided
or not all positional arguments are Future-like objects
and there is no running event loop.


Shielding From Cancellation
===========================
Expand Down Expand Up @@ -434,6 +439,10 @@ Shielding From Cancellation
except CancelledError:
res = None

.. deprecated:: 3.10
Deprecation warning is emitted if *aw* is not Future-like object
and there is no running event loop.


Timeouts
========
Expand Down Expand Up @@ -593,6 +602,10 @@ Waiting Primitives
earliest_result = await coro
# ...

.. deprecated:: 3.10
Deprecation warning is emitted if not all awaitable objects in the *aws*
iterable are Future-like objects and there is no running event loop.


Running in Threads
==================
Expand Down Expand Up @@ -775,6 +788,10 @@ Task Object
.. deprecated-removed:: 3.8 3.10
The *loop* parameter.

.. deprecated:: 3.10
Deprecation warning is emitted if *loop* is not specified
and there is no running event loop.

.. method:: cancel(msg=None)

Request the Task to be cancelled.
Expand Down
13 changes: 13 additions & 0 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1075,6 +1075,19 @@ Deprecated
scheduled for removal in Python 3.12.
(Contributed by Erlend E. Aasland in :issue:`42264`.)

* :func:`asyncio.get_event_loop` emits now a deprecation warning if there is
no running event loop. In future it will be an alias of
:func:`~asyncio.get_running_loop`.
:mod:`asyncio` functions which implicitly create a :class:`~asyncio.Future`
or :class:`~asyncio.Task` objects emit now
a deprecation warning if there is no running event loop and no explicit
*loop* argument is passed: :func:`~asyncio.ensure_future`,
:func:`~asyncio.wrap_future`, :func:`~asyncio.gather`,
:func:`~asyncio.shield`, :func:`~asyncio.as_completed` and constructors of
:class:`~asyncio.Future`, :class:`~asyncio.Task`,
:class:`~asyncio.StreamReader`, :class:`~asyncio.StreamReaderProtocol`.
(Contributed by Serhiy Storchaka in :issue:`39529`.)

* The undocumented built-in function ``sqlite3.enable_shared_cache`` is now
deprecated, scheduled for removal in Python 3.12. Its use is strongly
discouraged by the SQLite3 documentation. See `the SQLite3 docs
Expand Down
11 changes: 10 additions & 1 deletion Lib/asyncio/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -759,9 +759,16 @@ def get_event_loop():
the result of `get_event_loop_policy().get_event_loop()` call.
"""
# NOTE: this function is implemented in C (see _asynciomodule.c)
return _py__get_event_loop()


def _get_event_loop(stacklevel=3):
current_loop = _get_running_loop()
if current_loop is not None:
return current_loop
import warnings
warnings.warn('There is no current event loop',
DeprecationWarning, stacklevel=stacklevel)
return get_event_loop_policy().get_event_loop()


Expand Down Expand Up @@ -791,14 +798,15 @@ def set_child_watcher(watcher):
_py__set_running_loop = _set_running_loop
_py_get_running_loop = get_running_loop
_py_get_event_loop = get_event_loop
_py__get_event_loop = _get_event_loop


try:
# get_event_loop() is one of the most frequently called
# functions in asyncio. Pure Python implementation is
# about 4 times slower than C-accelerated.
from _asyncio import (_get_running_loop, _set_running_loop,
get_running_loop, get_event_loop)
get_running_loop, get_event_loop, _get_event_loop)
except ImportError:
pass
else:
Expand All @@ -807,3 +815,4 @@ def set_child_watcher(watcher):
_c__set_running_loop = _set_running_loop
_c_get_running_loop = get_running_loop
_c_get_event_loop = get_event_loop
_c__get_event_loop = _get_event_loop
4 changes: 2 additions & 2 deletions Lib/asyncio/futures.py
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ def __init__(self, *, loop=None):
the default event loop.
"""
if loop is None:
self._loop = events.get_event_loop()
self._loop = events._get_event_loop()
else:
self._loop = loop
self._callbacks = []
Expand Down Expand Up @@ -408,7 +408,7 @@ def wrap_future(future, *, loop=None):
assert isinstance(future, concurrent.futures.Future), \
f'concurrent.futures.Future is expected, got {future!r}'
if loop is None:
loop = events.get_event_loop()
loop = events._get_event_loop()
new_future = loop.create_future()
_chain_future(future, new_future)
return new_future
Expand Down
14 changes: 9 additions & 5 deletions Lib/asyncio/streams.py
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,7 @@ class FlowControlMixin(protocols.Protocol):

def __init__(self, loop=None):
if loop is None:
self._loop = events.get_event_loop()
self._loop = events._get_event_loop(stacklevel=4)
else:
self._loop = loop
self._paused = False
Expand Down Expand Up @@ -283,9 +283,13 @@ def _get_close_waiter(self, stream):
def __del__(self):
# Prevent reports about unhandled exceptions.
# Better than self._closed._log_traceback = False hack
closed = self._closed
if closed.done() and not closed.cancelled():
closed.exception()
try:
closed = self._closed
except AttributeError:
pass # failed constructor
else:
if closed.done() and not closed.cancelled():
closed.exception()


class StreamWriter:
Expand Down Expand Up @@ -381,7 +385,7 @@ def __init__(self, limit=_DEFAULT_LIMIT, loop=None):

self._limit = limit
if loop is None:
self._loop = events.get_event_loop()
self._loop = events._get_event_loop()
else:
self._loop = loop
self._buffer = bytearray()
Expand Down
42 changes: 23 additions & 19 deletions Lib/asyncio/tasks.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,7 @@ def as_completed(fs, *, timeout=None):
from .queues import Queue # Import here to avoid circular import problem.
done = Queue()

loop = events.get_event_loop()
loop = events._get_event_loop()
todo = {ensure_future(f, loop=loop) for f in set(fs)}
timeout_handle = None

Expand Down Expand Up @@ -616,23 +616,26 @@ def ensure_future(coro_or_future, *, loop=None):

If the argument is a Future, it is returned directly.
"""
if coroutines.iscoroutine(coro_or_future):
if loop is None:
loop = events.get_event_loop()
task = loop.create_task(coro_or_future)
if task._source_traceback:
del task._source_traceback[-1]
Copy link
Member Author

Choose a reason for hiding this comment

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

It was removed because this code did not work as intented long time ago (if worked at all).

return task
elif futures.isfuture(coro_or_future):
return _ensure_future(coro_or_future, loop=loop)


def _ensure_future(coro_or_future, *, loop=None):
if futures.isfuture(coro_or_future):
if loop is not None and loop is not futures._get_loop(coro_or_future):
raise ValueError('The future belongs to a different loop than '
'the one specified as the loop argument')
'the one specified as the loop argument')
return coro_or_future
elif inspect.isawaitable(coro_or_future):
return ensure_future(_wrap_awaitable(coro_or_future), loop=loop)
else:
raise TypeError('An asyncio.Future, a coroutine or an awaitable is '
'required')

if not coroutines.iscoroutine(coro_or_future):
if inspect.isawaitable(coro_or_future):
coro_or_future = _wrap_awaitable(coro_or_future)
else:
raise TypeError('An asyncio.Future, a coroutine or an awaitable '
'is required')

if loop is None:
loop = events._get_event_loop(stacklevel=4)
return loop.create_task(coro_or_future)


@types.coroutine
Expand All @@ -655,7 +658,8 @@ class _GatheringFuture(futures.Future):
cancelled.
"""

def __init__(self, children, *, loop=None):
def __init__(self, children, *, loop):
assert loop is not None
super().__init__(loop=loop)
self._children = children
self._cancel_requested = False
Expand Down Expand Up @@ -706,7 +710,7 @@ def gather(*coros_or_futures, return_exceptions=False):
gather won't cancel any other awaitables.
"""
if not coros_or_futures:
loop = events.get_event_loop()
loop = events._get_event_loop()
outer = loop.create_future()
outer.set_result([])
return outer
Expand Down Expand Up @@ -773,7 +777,7 @@ def _done_callback(fut):
loop = None
for arg in coros_or_futures:
if arg not in arg_to_fut:
fut = ensure_future(arg, loop=loop)
fut = _ensure_future(arg, loop=loop)
if loop is None:
loop = futures._get_loop(fut)
if fut is not arg:
Expand Down Expand Up @@ -823,7 +827,7 @@ def shield(arg):
except CancelledError:
res = None
"""
inner = ensure_future(arg)
inner = _ensure_future(arg)
if inner.done():
# Shortcut.
return inner
Expand Down
77 changes: 67 additions & 10 deletions Lib/test/test_asyncio/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -2702,14 +2702,18 @@ def get_event_loop(self):
asyncio.set_event_loop_policy(Policy())
loop = asyncio.new_event_loop()

with self.assertRaises(TestError):
asyncio.get_event_loop()
with self.assertWarns(DeprecationWarning) as cm:
with self.assertRaises(TestError):
asyncio.get_event_loop()
self.assertEqual(cm.warnings[0].filename, __file__)
asyncio.set_event_loop(None)
with self.assertRaises(TestError):
asyncio.get_event_loop()
with self.assertWarns(DeprecationWarning) as cm:
with self.assertRaises(TestError):
asyncio.get_event_loop()
self.assertEqual(cm.warnings[0].filename, __file__)

with self.assertRaisesRegex(RuntimeError, 'no running'):
self.assertIs(asyncio.get_running_loop(), None)
asyncio.get_running_loop()
self.assertIs(asyncio._get_running_loop(), None)

async def func():
Expand All @@ -2720,20 +2724,73 @@ async def func():
loop.run_until_complete(func())

asyncio.set_event_loop(loop)
with self.assertRaises(TestError):
asyncio.get_event_loop()
with self.assertWarns(DeprecationWarning) as cm:
with self.assertRaises(TestError):
asyncio.get_event_loop()
self.assertEqual(cm.warnings[0].filename, __file__)

asyncio.set_event_loop(None)
with self.assertRaises(TestError):
asyncio.get_event_loop()
with self.assertWarns(DeprecationWarning) as cm:
with self.assertRaises(TestError):
asyncio.get_event_loop()
self.assertEqual(cm.warnings[0].filename, __file__)

finally:
asyncio.set_event_loop_policy(old_policy)
if loop is not None:
loop.close()

with self.assertRaisesRegex(RuntimeError, 'no running'):
self.assertIs(asyncio.get_running_loop(), None)
asyncio.get_running_loop()

self.assertIs(asyncio._get_running_loop(), None)

def test_get_event_loop_returns_running_loop2(self):
old_policy = asyncio.get_event_loop_policy()
try:
asyncio.set_event_loop_policy(asyncio.DefaultEventLoopPolicy())
loop = asyncio.new_event_loop()
self.addCleanup(loop.close)

with self.assertWarns(DeprecationWarning) as cm:
loop2 = asyncio.get_event_loop()
self.addCleanup(loop2.close)
self.assertEqual(cm.warnings[0].filename, __file__)
asyncio.set_event_loop(None)
with self.assertWarns(DeprecationWarning) as cm:
with self.assertRaisesRegex(RuntimeError, 'no current'):
asyncio.get_event_loop()
self.assertEqual(cm.warnings[0].filename, __file__)

with self.assertRaisesRegex(RuntimeError, 'no running'):
asyncio.get_running_loop()
self.assertIs(asyncio._get_running_loop(), None)

async def func():
self.assertIs(asyncio.get_event_loop(), loop)
self.assertIs(asyncio.get_running_loop(), loop)
self.assertIs(asyncio._get_running_loop(), loop)

loop.run_until_complete(func())

asyncio.set_event_loop(loop)
with self.assertWarns(DeprecationWarning) as cm:
self.assertIs(asyncio.get_event_loop(), loop)
self.assertEqual(cm.warnings[0].filename, __file__)

asyncio.set_event_loop(None)
with self.assertWarns(DeprecationWarning) as cm:
with self.assertRaisesRegex(RuntimeError, 'no current'):
asyncio.get_event_loop()
self.assertEqual(cm.warnings[0].filename, __file__)

finally:
asyncio.set_event_loop_policy(old_policy)
if loop is not None:
loop.close()

with self.assertRaisesRegex(RuntimeError, 'no running'):
asyncio.get_running_loop()

self.assertIs(asyncio._get_running_loop(), None)

Expand Down
Loading