Skip to content

Commit

Permalink
bpo-39529: Deprecate creating new event loop in asyncio.get_event_loo…
Browse files Browse the repository at this point in the history
…p() (pythonGH-23554)

asyncio.get_event_loop() emits now a deprecation warning when it creates a new event loop.
In future releases it will became an alias of asyncio.get_running_loop().
  • Loading branch information
serhiy-storchaka authored Apr 25, 2021
1 parent face87c commit 172c0f2
Show file tree
Hide file tree
Showing 16 changed files with 568 additions and 183 deletions.
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 @@ -1349,6 +1349,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]
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

0 comments on commit 172c0f2

Please sign in to comment.