gh-100160: Restore and deprecate implicit creation of an event loop (GH-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>
This commit is contained in:
Serhiy Storchaka 2023-01-13 14:40:29 +02:00 committed by GitHub
parent 468c3bf798
commit e5bd5ad70d
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
8 changed files with 77 additions and 41 deletions

View File

@ -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
@ -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)

View File

@ -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

View File

@ -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

View File

@ -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
------------------------------
@ -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
=============

View File

@ -619,7 +619,7 @@ class AbstractEventLoopPolicy:
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
@ -672,6 +672,28 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):
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)

View File

@ -2614,8 +2614,33 @@ class PolicyTests(unittest.TestCase):
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()
@ -2801,8 +2826,10 @@ class GetEventLoopTestsMixin:
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()

View File

@ -1884,7 +1884,9 @@ class TestFork(unittest.IsolatedAsyncioTestCase):
if pid == 0:
# child
try:
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:
@ -1893,7 +1895,9 @@ class TestFork(unittest.IsolatedAsyncioTestCase):
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')

View File

@ -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.