Skip to content
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

gh-126353: remove implicit creation of loop from get_event_loop #126354

Merged
merged 11 commits into from
Nov 4, 2024
5 changes: 2 additions & 3 deletions Doc/library/asyncio-eventloop.rst
Original file line number Diff line number Diff line change
Expand Up @@ -59,9 +59,8 @@ an event loop:
instead of using these lower level functions to manually create and close an
event loop.

.. deprecated:: 3.12
Deprecation warning is emitted if there is no current event loop.
In some future Python release this will become an error.
.. versionchanged:: 3.14
Raises a :exc:`RuntimeError` if there is no current event loop.

.. function:: set_event_loop(loop)

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

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

.. 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.
.. versionchanged:: 3.14
The :meth:`get_event_loop` method of the default asyncio policy now
raises a :exc:`RuntimeError` if there is no set event loop.


.. class:: WindowsSelectorEventLoopPolicy
Expand Down
5 changes: 5 additions & 0 deletions Doc/whatsnew/3.14.rst
Original file line number Diff line number Diff line change
Expand Up @@ -576,6 +576,11 @@ asyncio

(Contributed by Kumar Aditya in :gh:`120804`.)

* Removed implicit creation of event loop by :func:`asyncio.get_event_loop`.
It now raises a :exc:`RuntimeError` if there is no current event loop.
(Contributed by Kumar Aditya in :gh:`126353`.)


collections.abc
---------------

Expand Down
24 changes: 0 additions & 24 deletions Lib/asyncio/events.py
Original file line number Diff line number Diff line change
Expand Up @@ -668,7 +668,6 @@ class BaseDefaultEventLoopPolicy(AbstractEventLoopPolicy):

class _Local(threading.local):
_loop = None
_set_called = False

def __init__(self):
self._local = self._Local()
Expand All @@ -678,28 +677,6 @@ 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 All @@ -708,7 +685,6 @@ def get_event_loop(self):

def set_event_loop(self, loop):
"""Set the event loop."""
self._local._set_called = True
if loop is not None and not isinstance(loop, AbstractEventLoop):
raise TypeError(f"loop must be an instance of AbstractEventLoop or None, not '{type(loop).__name__}'")
self._local._loop = loop
Expand Down
34 changes: 9 additions & 25 deletions Lib/test/test_asyncio/test_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -2704,33 +2704,22 @@ def test_event_loop_policy(self):
def test_get_event_loop(self):
kumaraditya303 marked this conversation as resolved.
Show resolved Hide resolved
policy = asyncio.DefaultEventLoopPolicy()
self.assertIsNone(policy._local._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()
with self.assertRaises(RuntimeError):
loop = policy.get_event_loop()
self.assertIsNone(policy._local._loop)

def test_get_event_loop_calls_set_event_loop(self):
kumaraditya303 marked this conversation as resolved.
Show resolved Hide resolved
def test_get_event_loop_does_not_call_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:
with self.assertRaises(RuntimeError):
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()
m_set_event_loop.assert_not_called()

def test_get_event_loop_after_set_none(self):
policy = asyncio.DefaultEventLoopPolicy()
Expand Down Expand Up @@ -2912,17 +2901,12 @@ def test_get_event_loop_returns_running_loop2(self):
loop = asyncio.new_event_loop()
self.addCleanup(loop.close)

with self.assertWarns(DeprecationWarning) as cm:
kumaraditya303 marked this conversation as resolved.
Show resolved Hide resolved
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()

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

async def func():
self.assertIs(asyncio.get_event_loop(), loop)
Expand Down
6 changes: 2 additions & 4 deletions Lib/test/test_asyncio/test_unix_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -1195,8 +1195,7 @@ async def test_fork_not_share_event_loop(self):
if pid == 0:
# child
try:
with self.assertWarns(DeprecationWarning):
loop = asyncio.get_event_loop_policy().get_event_loop()
loop = asyncio.get_event_loop()
serhiy-storchaka marked this conversation as resolved.
Show resolved Hide resolved
os.write(w, b'LOOP:' + str(id(loop)).encode())
except RuntimeError:
os.write(w, b'NO LOOP')
Expand All @@ -1207,8 +1206,7 @@ async def test_fork_not_share_event_loop(self):
else:
# parent
result = os.read(r, 100)
self.assertEqual(result[:5], b'LOOP:', result)
self.assertNotEqual(int(result[5:]), id(loop))
self.assertEqual(result, b'NO LOOP')
wait_process(pid, exitcode=0)

@hashlib_helper.requires_hashdigest('md5')
Expand Down
2 changes: 1 addition & 1 deletion Lib/test/test_cmd_line.py
Original file line number Diff line number Diff line change
Expand Up @@ -924,7 +924,7 @@ def test_python_gil(self):
self.assertEqual(proc.stderr, '')

def test_python_asyncio_debug(self):
code = "import asyncio; print(asyncio.get_event_loop().get_debug())"
code = "import asyncio; print(asyncio.new_event_loop().get_debug())"
Copy link
Contributor

Choose a reason for hiding this comment

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

I think this needs a with contextlib.closing(... to prevent the test suite printing a ResourceWarning

rc, out, err = assert_python_ok('-c', code, PYTHONASYNCIODEBUG='1')
self.assertIn(b'True', out)

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
:func:`asyncio.get_event_loop` now does not implicitly creates an event loop.
It now raises a :exc:`RuntimeError` if there is no set event loop. Patch by Kumar Aditya.
Loading