Skip to content

Commit

Permalink
gh-100160: Restore and deprecate implicit creation of an event loop (G…
Browse files Browse the repository at this point in the history
…H-100410)

Partially revert changes made in GH-93453.

asyncio.DefaultEventLoopPolicy.get_event_loop() now emits a
DeprecationWarning and creates and sets a new event loop instead of
raising a RuntimeError if there is no current event loop set.

Co-authored-by: Guido van Rossum <gvanrossum@gmail.com>
  • Loading branch information
serhiy-storchaka and gvanrossum authored Jan 13, 2023
1 parent 468c3bf commit e5bd5ad
Show file tree
Hide file tree
Showing 8 changed files with 77 additions and 41 deletions.
10 changes: 4 additions & 6 deletions Doc/library/asyncio-eventloop.rst
Original file line number Diff line number Diff line change
Expand Up @@ -48,7 +48,7 @@ an event loop:
running event loop.

If there is no running event loop set, the function will return
the result of calling ``get_event_loop_policy().get_event_loop()``.
the result of the ``get_event_loop_policy().get_event_loop()`` call.

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

.. note::
In Python versions 3.10.0--3.10.8 and 3.11.0 this function
(and other functions which used it implicitly) emitted a
:exc:`DeprecationWarning` if there was no running event loop, even if
the current loop was set.
.. deprecated:: 3.12
Deprecation warning is emitted if there is no current event loop.
In some future Python release this will become an error.

.. function:: set_event_loop(loop)

Expand Down
8 changes: 5 additions & 3 deletions Doc/library/asyncio-policy.rst
Original file line number Diff line number Diff line change
Expand Up @@ -116,9 +116,11 @@ asyncio ships with the following built-in policies:

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

.. versionchanged:: 3.12
:meth:`get_event_loop` now raises a :exc:`RuntimeError` if there is no
current event loop set.
.. deprecated:: 3.12
The :meth:`get_event_loop` method of the default asyncio policy now emits
a :exc:`DeprecationWarning` if there is no current event loop set and it
decides to create one.
In some future Python release this will become an error.


.. class:: WindowsSelectorEventLoopPolicy
Expand Down
13 changes: 0 additions & 13 deletions Doc/whatsnew/3.10.rst
Original file line number Diff line number Diff line change
Expand Up @@ -1709,19 +1709,6 @@ Deprecated
scheduled for removal in Python 3.12.
(Contributed by Erlend E. Aasland in :issue:`42264`.)
* :func:`asyncio.get_event_loop` now emits a deprecation warning if there is
no running event loop. In the future it will be an alias of
:func:`~asyncio.get_running_loop`.
:mod:`asyncio` functions which implicitly create :class:`~asyncio.Future`
or :class:`~asyncio.Task` objects now emit
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
17 changes: 5 additions & 12 deletions Doc/whatsnew/3.12.rst
Original file line number Diff line number Diff line change
Expand Up @@ -408,6 +408,11 @@ Deprecated
:exc:`ImportWarning`).
(Contributed by Brett Cannon in :gh:`65961`.)

* The :meth:`~asyncio.DefaultEventLoopPolicy.get_event_loop` method of the
default event loop policy now emits a :exc:`DeprecationWarning` if there
is no current event loop set and it decides to create one.
(Contributed by Serhiy Storchaka and Guido van Rossum in :gh:`100160`.)


Pending Removal in Python 3.13
------------------------------
Expand Down Expand Up @@ -710,18 +715,6 @@ Changes in the Python API
around process-global resources, which are best managed from the main interpreter.
(Contributed by Dong-hee Na in :gh:`99127`.)

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


Build Changes
=============
Expand Down
24 changes: 23 additions & 1 deletion Lib/asyncio/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -619,7 +619,7 @@ def get_event_loop(self):
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.
current context and the current policy does not specify to create one.
It should never return None."""
raise NotImplementedError
Expand Down Expand Up @@ -672,6 +672,28 @@ def get_event_loop(self):
Returns an instance of EventLoop or raises an exception.
"""
if (self._local._loop is None and
not self._local._set_called and
threading.current_thread() is threading.main_thread()):
stacklevel = 2
try:
f = sys._getframe(1)
except AttributeError:
pass
else:
# Move up the call stack so that the warning is attached
# to the line outside asyncio itself.
while f:
module = f.f_globals.get('__name__')
if not (module == 'asyncio' or module.startswith('asyncio.')):
break
f = f.f_back
stacklevel += 1
import warnings
warnings.warn('There is no current event loop',
DeprecationWarning, stacklevel=stacklevel)
self.set_event_loop(self.new_event_loop())

if self._local._loop is None:
raise RuntimeError('There is no current event loop in thread %r.'
% threading.current_thread().name)
Expand Down
35 changes: 31 additions & 4 deletions Lib/test/test_asyncio/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -2614,8 +2614,33 @@ def test_event_loop_policy(self):
def test_get_event_loop(self):
policy = asyncio.DefaultEventLoopPolicy()
self.assertIsNone(policy._local._loop)
with self.assertRaisesRegex(RuntimeError, 'no current event loop'):
policy.get_event_loop()
with self.assertWarns(DeprecationWarning) as cm:
loop = policy.get_event_loop()
self.assertEqual(cm.filename, __file__)
self.assertIsInstance(loop, asyncio.AbstractEventLoop)

self.assertIs(policy._local._loop, loop)
self.assertIs(loop, policy.get_event_loop())
loop.close()

def test_get_event_loop_calls_set_event_loop(self):
policy = asyncio.DefaultEventLoopPolicy()

with mock.patch.object(
policy, "set_event_loop",
wraps=policy.set_event_loop) as m_set_event_loop:

with self.assertWarns(DeprecationWarning) as cm:
loop = policy.get_event_loop()
self.addCleanup(loop.close)
self.assertEqual(cm.filename, __file__)

# policy._local._loop must be set through .set_event_loop()
# (the unix DefaultEventLoopPolicy needs this call to attach
# the child watcher correctly)
m_set_event_loop.assert_called_with(loop)

loop.close()

def test_get_event_loop_after_set_none(self):
policy = asyncio.DefaultEventLoopPolicy()
Expand Down Expand Up @@ -2801,8 +2826,10 @@ def test_get_event_loop_returns_running_loop2(self):
loop = asyncio.new_event_loop()
self.addCleanup(loop.close)

with self.assertRaisesRegex(RuntimeError, 'no current'):
asyncio.get_event_loop()
with self.assertWarns(DeprecationWarning) as cm:
loop2 = asyncio.get_event_loop()
self.addCleanup(loop2.close)
self.assertEqual(cm.filename, __file__)
asyncio.set_event_loop(None)
with self.assertRaisesRegex(RuntimeError, 'no current'):
asyncio.get_event_loop()
Expand Down
8 changes: 6 additions & 2 deletions Lib/test/test_asyncio/test_unix_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1884,7 +1884,9 @@ async def test_fork_not_share_event_loop(self):
if pid == 0:
# child
try:
loop = asyncio.get_event_loop_policy().get_event_loop()
with self.assertWarns(DeprecationWarning):
loop = asyncio.get_event_loop_policy().get_event_loop()
os.write(w, b'LOOP:' + str(id(loop)).encode())
except RuntimeError:
os.write(w, b'NO LOOP')
except:
Expand All @@ -1893,7 +1895,9 @@ async def test_fork_not_share_event_loop(self):
os._exit(0)
else:
# parent
self.assertEqual(os.read(r, 100), b'NO LOOP')
result = os.read(r, 100)
self.assertEqual(result[:5], b'LOOP:', result)
self.assertNotEqual(int(result[5:]), id(loop))
wait_process(pid, exitcode=0)

@hashlib_helper.requires_hashdigest('md5')
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
Emit a deprecation warning in
:meth:`asyncio.DefaultEventLoopPolicy.get_event_loop` if there is no current
event loop set and it decides to create one.

0 comments on commit e5bd5ad

Please sign in to comment.