From fe6d8db06db7344893acfa50bcb98830f0a78cdb Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sat, 28 Nov 2020 11:05:35 +0200 Subject: [PATCH 1/9] Deprecate creating new event loop in asyncio.get_event_loop() --- Lib/asyncio/events.py | 11 +++++- Lib/asyncio/futures.py | 6 +-- Lib/asyncio/streams.py | 12 +++--- Lib/asyncio/subprocess.py | 4 +- Lib/asyncio/tasks.py | 49 ++++++++++++----------- Lib/test/test_asyncio/test_events.py | 16 ++++++-- Lib/test/test_asyncio/test_futures.py | 9 ++++- Lib/test/test_asyncio/test_queues.py | 23 ++++++----- Lib/test/test_asyncio/test_streams.py | 10 +++-- Lib/test/test_asyncio/test_tasks.py | 56 +++++++++++++++++++++------ Modules/_asynciomodule.c | 26 +++++++++++-- Modules/clinic/_asynciomodule.c.h | 41 +++++++++++++++++++- 12 files changed, 196 insertions(+), 67 deletions(-) diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index 1a20f362ec3869..11f0b7f84bdeb7 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -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 running event loop set", + DeprecationWarning, stacklevel=stacklevel) return get_event_loop_policy().get_event_loop() @@ -791,6 +798,7 @@ 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: @@ -798,7 +806,7 @@ def set_child_watcher(watcher): # 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: @@ -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 diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py index bed4da52fd4d98..87e5fc66f605e0 100644 --- a/Lib/asyncio/futures.py +++ b/Lib/asyncio/futures.py @@ -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 = [] @@ -371,7 +371,7 @@ def _chain_future(source, destination): raise TypeError('A future is required for source argument') if not isfuture(destination) and not isinstance(destination, concurrent.futures.Future): - raise TypeError('A future is required for destination argument') + raise TypeError('A future is required for destination argument %r' % destination) source_loop = _get_loop(source) if isfuture(source) else None dest_loop = _get_loop(destination) if isfuture(destination) else None @@ -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 diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index 96a9f97200d0d9..bb706ecad74121 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -41,7 +41,7 @@ async def open_connection(host=None, port=None, *, StreamReaderProtocol classes, just copy the code -- there's really nothing special here except some convenience.) """ - loop = events.get_running_loop() + loop = events._get_event_loop() reader = StreamReader(limit=limit, loop=loop) protocol = StreamReaderProtocol(reader, loop=loop) transport, _ = await loop.create_connection( @@ -73,7 +73,7 @@ async def start_server(client_connected_cb, host=None, port=None, *, The return value is the same as loop.create_server(), i.e. a Server object which can be used to stop the service. """ - loop = events.get_running_loop() + loop = events._get_event_loop() def factory(): reader = StreamReader(limit=limit, loop=loop) @@ -90,7 +90,7 @@ def factory(): async def open_unix_connection(path=None, *, limit=_DEFAULT_LIMIT, **kwds): """Similar to `open_connection` but works with UNIX Domain Sockets.""" - loop = events.get_running_loop() + loop = events._get_event_loop() reader = StreamReader(limit=limit, loop=loop) protocol = StreamReaderProtocol(reader, loop=loop) @@ -102,7 +102,7 @@ async def open_unix_connection(path=None, *, async def start_unix_server(client_connected_cb, path=None, *, limit=_DEFAULT_LIMIT, **kwds): """Similar to `start_server` but works with UNIX Domain Sockets.""" - loop = events.get_running_loop() + loop = events._get_event_loop() def factory(): reader = StreamReader(limit=limit, loop=loop) @@ -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 @@ -381,7 +381,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() diff --git a/Lib/asyncio/subprocess.py b/Lib/asyncio/subprocess.py index cd10231f710f11..92483595307620 100644 --- a/Lib/asyncio/subprocess.py +++ b/Lib/asyncio/subprocess.py @@ -199,7 +199,7 @@ async def communicate(self, input=None): async def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None, limit=streams._DEFAULT_LIMIT, **kwds): - loop = events.get_running_loop() + loop = events._get_event_loop() protocol_factory = lambda: SubprocessStreamProtocol(limit=limit, loop=loop) transport, protocol = await loop.subprocess_shell( @@ -212,7 +212,7 @@ async def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None, async def create_subprocess_exec(program, *args, stdin=None, stdout=None, stderr=None, limit=streams._DEFAULT_LIMIT, **kwds): - loop = events.get_running_loop() + loop = events._get_event_loop() protocol_factory = lambda: SubprocessStreamProtocol(limit=limit, loop=loop) transport, protocol = await loop.subprocess_exec( diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index eef7f8808eb06f..9e34538c7c6103 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -66,7 +66,7 @@ def _all_tasks_compat(loop=None): # the completed ones. Used to implement deprecated "Tasks.all_task()" # method. if loop is None: - loop = events.get_event_loop() + loop = events._get_event_loop() # Looping over a WeakSet (_all_tasks) isn't safe as it can be updated from another # thread while we do so. Therefore we cast it to list prior to filtering. The list # cast itself requires iteration, so we repeat it several times ignoring @@ -570,7 +570,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 @@ -637,23 +637,28 @@ 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): - 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') - 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') + return _ensure_future(coro_or_future, loop=loop) + + +def _ensure_future(coro_or_future, *, loop=None): + while True: + if coroutines.iscoroutine(coro_or_future): + if loop is None: + loop = events._get_event_loop(stacklevel=4) + task = loop.create_task(coro_or_future) + if task._source_traceback: + del task._source_traceback[-1] + return task + elif 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') + return coro_or_future + elif 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') @types.coroutine @@ -727,7 +732,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 @@ -794,7 +799,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: @@ -844,7 +849,7 @@ def shield(arg): except CancelledError: res = None """ - inner = ensure_future(arg) + inner = _ensure_future(arg) if inner.done(): # Shortcut. return inner diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index 7f76011d2b92dc..215f6a36c4c6f1 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -2699,10 +2699,14 @@ def get_event_loop(self): loop = asyncio.new_event_loop() with self.assertRaises(TestError): - asyncio.get_event_loop() + with self.assertWarns(DeprecationWarning) as cm: + 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: + asyncio.get_event_loop() + self.assertEqual(cm.warnings[0].filename, __file__) with self.assertRaisesRegex(RuntimeError, 'no running'): self.assertIs(asyncio.get_running_loop(), None) @@ -2717,11 +2721,15 @@ async def func(): asyncio.set_event_loop(loop) with self.assertRaises(TestError): - asyncio.get_event_loop() + with self.assertWarns(DeprecationWarning) as cm: + 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: + asyncio.get_event_loop() + self.assertEqual(cm.warnings[0].filename, __file__) finally: asyncio.set_event_loop_policy(old_policy) diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py index ec00896cc620b3..4d2df6d203edd2 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -102,7 +102,13 @@ def test_ensure_future(self): class BaseFutureTests: def _new_future(self, *args, **kwargs): - return self.cls(*args, **kwargs) + if kwargs.get('loop') is None: + with self.assertWarns(DeprecationWarning) as cm: + fut = self.cls(*args, **kwargs) + self.assertEqual(cm.warnings[0].filename, __file__) + return fut + else: + return self.cls(*args, **kwargs) def setUp(self): super().setUp() @@ -475,6 +481,7 @@ def test_wrap_future_future(self): def test_wrap_future_use_global_loop(self): with mock.patch('asyncio.futures.events') as events: events.get_event_loop = lambda: self.loop + events._get_event_loop = lambda: self.loop def run(arg): return (arg, threading.get_ident()) ex = concurrent.futures.ThreadPoolExecutor(1) diff --git a/Lib/test/test_asyncio/test_queues.py b/Lib/test/test_asyncio/test_queues.py index 0a0b529f621b26..63a9a5f270cc92 100644 --- a/Lib/test/test_asyncio/test_queues.py +++ b/Lib/test/test_asyncio/test_queues.py @@ -273,12 +273,12 @@ async def create_queue(): queue._get_loop() return queue - q = self.loop.run_until_complete(create_queue()) + async def test(): + q = await create_queue() + await asyncio.gather(producer(q, producer_num_items), + consumer(q, producer_num_items)) - self.loop.run_until_complete( - asyncio.gather(producer(q, producer_num_items), - consumer(q, producer_num_items)), - ) + self.loop.run_until_complete(test()) def test_cancelled_getters_not_being_held_in_self_getters(self): def a_generator(): @@ -516,11 +516,14 @@ async def getter(): for _ in range(num): item = queue.get_nowait() - t0 = putter(0) - t1 = putter(1) - t2 = putter(2) - t3 = putter(3) - self.loop.run_until_complete(asyncio.gather(getter(), t0, t1, t2, t3)) + async def test(): + t0 = putter(0) + t1 = putter(1) + t2 = putter(2) + t3 = putter(3) + await asyncio.gather(getter(), t0, t1, t2, t3) + + self.loop.run_until_complete(test()) def test_cancelled_puts_not_being_held_in_self_putters(self): def a_generator(): diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index a6cfcbfb57b361..531ca5b76c212f 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -43,7 +43,7 @@ def tearDown(self): @mock.patch('asyncio.streams.events') def test_ctor_global_loop(self, m_events): stream = asyncio.StreamReader() - self.assertIs(stream._loop, m_events.get_event_loop.return_value) + self.assertIs(stream._loop, m_events._get_event_loop.return_value) def _basetest_open_connection(self, open_connection_fut): messages = [] @@ -755,7 +755,9 @@ def test_streamreader_constructor(self): # asyncio issue #184: Ensure that StreamReaderProtocol constructor # retrieves the current loop if the loop parameter is not set - reader = asyncio.StreamReader() + with self.assertWarns(DeprecationWarning) as cm: + reader = asyncio.StreamReader() + self.assertEqual(cm.warnings[0].filename, __file__) self.assertIs(reader._loop, self.loop) def test_streamreaderprotocol_constructor(self): @@ -765,7 +767,9 @@ def test_streamreaderprotocol_constructor(self): # asyncio issue #184: Ensure that StreamReaderProtocol constructor # retrieves the current loop if the loop parameter is not set reader = mock.Mock() - protocol = asyncio.StreamReaderProtocol(reader) + with self.assertWarns(DeprecationWarning) as cm: + protocol = asyncio.StreamReaderProtocol(reader) + self.assertEqual(cm.warnings[0].filename, __file__) self.assertIs(protocol._loop, self.loop) def test_drain_raises(self): diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 7c2e85ceefde13..4325fba9cbb827 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1685,7 +1685,9 @@ def gen(): b = asyncio.sleep(0.10, 'b') fs = {a, b} - futs = list(asyncio.as_completed(fs)) + with self.assertWarns(DeprecationWarning) as cm: + futs = list(asyncio.as_completed(fs)) + self.assertEqual(cm.warnings[0].filename, __file__) self.assertEqual(len(futs), 2) x = loop.run_until_complete(futs[1]) @@ -1712,7 +1714,9 @@ def gen(): b = asyncio.sleep(0.05, 'b') fs = {a, b} - futs = list(asyncio.as_completed(fs)) + with self.assertWarns(DeprecationWarning) as cm: + futs = list(asyncio.as_completed(fs)) + self.assertEqual(cm.warnings[0].filename, __file__) self.assertEqual(len(futs), 2) waiter = asyncio.wait(futs) # Deprecation from passing coros in futs to asyncio.wait() @@ -2515,7 +2519,9 @@ def test_cancel_gather_1(self): # gathering task is done at the same time as the child future def child_coro(): return (yield from fut) - gather_future = asyncio.gather(child_coro()) + with self.assertWarns(DeprecationWarning) as cm: + gather_future = asyncio.gather(child_coro()) + self.assertEqual(cm.warnings[0].filename, __file__) gather_task = asyncio.ensure_future(gather_future, loop=loop) cancel_result = None @@ -3222,7 +3228,12 @@ def _run_loop(self, loop): def _check_success(self, **kwargs): a, b, c = [self.one_loop.create_future() for i in range(3)] - fut = asyncio.gather(*self.wrap_futures(a, b, c), **kwargs) + if self.is_future: + fut = asyncio.gather(*self.wrap_futures(a, b, c), **kwargs) + else: + with self.assertWarns(DeprecationWarning) as cm: + fut = asyncio.gather(*self.wrap_futures(a, b, c), **kwargs) + self.assertEqual(cm.warnings[0].filename, __file__) cb = test_utils.MockCallback() fut.add_done_callback(cb) b.set_result(1) @@ -3244,7 +3255,12 @@ def test_result_exception_success(self): def test_one_exception(self): a, b, c, d, e = [self.one_loop.create_future() for i in range(5)] - fut = asyncio.gather(*self.wrap_futures(a, b, c, d, e)) + if self.is_future: + fut = asyncio.gather(*self.wrap_futures(a, b, c, d, e)) + else: + with self.assertWarns(DeprecationWarning) as cm: + fut = asyncio.gather(*self.wrap_futures(a, b, c, d, e)) + self.assertEqual(cm.warnings[0].filename, __file__) cb = test_utils.MockCallback() fut.add_done_callback(cb) exc = ZeroDivisionError() @@ -3262,8 +3278,14 @@ def test_one_exception(self): def test_return_exceptions(self): a, b, c, d = [self.one_loop.create_future() for i in range(4)] - fut = asyncio.gather(*self.wrap_futures(a, b, c, d), - return_exceptions=True) + if self.is_future: + fut = asyncio.gather(*self.wrap_futures(a, b, c, d), + return_exceptions=True) + else: + with self.assertWarns(DeprecationWarning) as cm: + fut = asyncio.gather(*self.wrap_futures(a, b, c, d), + return_exceptions=True) + self.assertEqual(cm.warnings[0].filename, __file__) cb = test_utils.MockCallback() fut.add_done_callback(cb) exc = ZeroDivisionError() @@ -3312,13 +3334,17 @@ def test_env_var_debug(self): class FutureGatherTests(GatherTestsBase, test_utils.TestCase): + is_future = True + def wrap_futures(self, *futures): return futures def _check_empty_sequence(self, seq_or_iter): asyncio.set_event_loop(self.one_loop) self.addCleanup(asyncio.set_event_loop, None) - fut = asyncio.gather(*seq_or_iter) + with self.assertWarns(DeprecationWarning) as cm: + fut = asyncio.gather(*seq_or_iter) + self.assertEqual(cm.warnings[0].filename, __file__) self.assertIsInstance(fut, asyncio.Future) self.assertIs(fut._loop, self.one_loop) self._run_loop(self.one_loop) @@ -3392,6 +3418,8 @@ def test_result_exception_one_cancellation(self): class CoroutineGatherTests(GatherTestsBase, test_utils.TestCase): + is_future = False + def setUp(self): super().setUp() asyncio.set_event_loop(self.one_loop) @@ -3409,7 +3437,9 @@ async def coro(): return 'abc' gen1 = coro() gen2 = coro() - fut = asyncio.gather(gen1, gen2) + with self.assertWarns(DeprecationWarning) as cm: + fut = asyncio.gather(gen1, gen2) + self.assertEqual(cm.warnings[0].filename, __file__) self.assertIs(fut._loop, self.one_loop) self.one_loop.run_until_complete(fut) @@ -3417,7 +3447,9 @@ async def coro(): asyncio.set_event_loop(self.other_loop) gen3 = coro() gen4 = coro() - fut2 = asyncio.gather(gen3, gen4) + with self.assertWarns(DeprecationWarning) as cm: + fut2 = asyncio.gather(gen3, gen4) + self.assertEqual(cm.warnings[0].filename, __file__) self.assertIs(fut2._loop, self.other_loop) self.other_loop.run_until_complete(fut2) @@ -3427,7 +3459,9 @@ def test_duplicate_coroutines(self): def coro(s): return s c = coro('abc') - fut = asyncio.gather(c, c, coro('def'), c) + with self.assertWarns(DeprecationWarning) as cm: + fut = asyncio.gather(c, c, coro('def'), c) + self.assertEqual(cm.warnings[0].filename, __file__) self._run_loop(self.one_loop) self.assertEqual(fut.result(), ['abc', 'abc', 'def', 'abc']) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index 01e36c656da8fb..b8c672f6cf5609 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -319,7 +319,7 @@ set_running_loop(PyObject *loop) static PyObject * -get_event_loop(void) +get_event_loop(int stacklevel) { PyObject *loop; PyObject *policy; @@ -331,6 +331,13 @@ get_event_loop(void) return loop; } + if (PyErr_WarnEx(PyExc_DeprecationWarning, + "There is no running event loop set", + stacklevel)) + { + return NULL; + } + policy = PyObject_CallNoArgs(asyncio_get_event_loop_policy); if (policy == NULL) { return NULL; @@ -489,7 +496,7 @@ future_init(FutureObj *fut, PyObject *loop) fut->fut_blocking = 0; if (loop == Py_None) { - loop = get_event_loop(); + loop = get_event_loop(1); if (loop == NULL) { return -1; } @@ -3078,7 +3085,19 @@ static PyObject * _asyncio_get_event_loop_impl(PyObject *module) /*[clinic end generated code: output=2a2d8b2f824c648b input=9364bf2916c8655d]*/ { - return get_event_loop(); + return get_event_loop(1); +} + +/*[clinic input] +_asyncio._get_event_loop + stacklevel: int = 3 +[clinic start generated code]*/ + +static PyObject * +_asyncio__get_event_loop_impl(PyObject *module, int stacklevel) +/*[clinic end generated code: output=9c1d6d3c802e67c9 input=d17aebbd686f711d]*/ +{ + return get_event_loop(stacklevel-1); } /*[clinic input] @@ -3375,6 +3394,7 @@ PyDoc_STRVAR(module_doc, "Accelerator module for asyncio"); static PyMethodDef asyncio_methods[] = { _ASYNCIO_GET_EVENT_LOOP_METHODDEF + _ASYNCIO__GET_EVENT_LOOP_METHODDEF _ASYNCIO_GET_RUNNING_LOOP_METHODDEF _ASYNCIO__GET_RUNNING_LOOP_METHODDEF _ASYNCIO__SET_RUNNING_LOOP_METHODDEF diff --git a/Modules/clinic/_asynciomodule.c.h b/Modules/clinic/_asynciomodule.c.h index a071efc1e2be3a..c472e652fb7c56 100644 --- a/Modules/clinic/_asynciomodule.c.h +++ b/Modules/clinic/_asynciomodule.c.h @@ -669,6 +669,45 @@ _asyncio_get_event_loop(PyObject *module, PyObject *Py_UNUSED(ignored)) return _asyncio_get_event_loop_impl(module); } +PyDoc_STRVAR(_asyncio__get_event_loop__doc__, +"_get_event_loop($module, /, stacklevel=3)\n" +"--\n" +"\n"); + +#define _ASYNCIO__GET_EVENT_LOOP_METHODDEF \ + {"_get_event_loop", (PyCFunction)(void(*)(void))_asyncio__get_event_loop, METH_FASTCALL|METH_KEYWORDS, _asyncio__get_event_loop__doc__}, + +static PyObject * +_asyncio__get_event_loop_impl(PyObject *module, int stacklevel); + +static PyObject * +_asyncio__get_event_loop(PyObject *module, PyObject *const *args, Py_ssize_t nargs, PyObject *kwnames) +{ + PyObject *return_value = NULL; + static const char * const _keywords[] = {"stacklevel", NULL}; + static _PyArg_Parser _parser = {NULL, _keywords, "_get_event_loop", 0}; + PyObject *argsbuf[1]; + Py_ssize_t noptargs = nargs + (kwnames ? PyTuple_GET_SIZE(kwnames) : 0) - 0; + int stacklevel = 3; + + args = _PyArg_UnpackKeywords(args, nargs, NULL, kwnames, &_parser, 0, 1, 0, argsbuf); + if (!args) { + goto exit; + } + if (!noptargs) { + goto skip_optional_pos; + } + stacklevel = _PyLong_AsInt(args[0]); + if (stacklevel == -1 && PyErr_Occurred()) { + goto exit; + } +skip_optional_pos: + return_value = _asyncio__get_event_loop_impl(module, stacklevel); + +exit: + return return_value; +} + PyDoc_STRVAR(_asyncio_get_running_loop__doc__, "get_running_loop($module, /)\n" "--\n" @@ -832,4 +871,4 @@ _asyncio__leave_task(PyObject *module, PyObject *const *args, Py_ssize_t nargs, exit: return return_value; } -/*[clinic end generated code: output=d0fc522bcbff9d61 input=a9049054013a1b77]*/ +/*[clinic end generated code: output=0d127162ac92e0c0 input=a9049054013a1b77]*/ From 1b57659c2c9c496c0c9b9dc9d7c873df644f407b Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Mon, 30 Nov 2020 10:08:55 +0200 Subject: [PATCH 2/9] Test with real running loop. --- Lib/asyncio/events.py | 2 +- Lib/asyncio/streams.py | 10 +- Lib/test/test_asyncio/test_events.py | 69 ++++++-- Lib/test/test_asyncio/test_futures.py | 31 +++- Lib/test/test_asyncio/test_streams.py | 47 ++++-- Lib/test/test_asyncio/test_tasks.py | 232 +++++++++++--------------- Modules/_asynciomodule.c | 2 +- 7 files changed, 228 insertions(+), 165 deletions(-) diff --git a/Lib/asyncio/events.py b/Lib/asyncio/events.py index 11f0b7f84bdeb7..b966ad26bf467b 100644 --- a/Lib/asyncio/events.py +++ b/Lib/asyncio/events.py @@ -767,7 +767,7 @@ def _get_event_loop(stacklevel=3): if current_loop is not None: return current_loop import warnings - warnings.warn("There is no running event loop set", + warnings.warn('There is no current event loop', DeprecationWarning, stacklevel=stacklevel) return get_event_loop_policy().get_event_loop() diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index bb706ecad74121..de2bf8472af9f1 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -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: diff --git a/Lib/test/test_asyncio/test_events.py b/Lib/test/test_asyncio/test_events.py index 215f6a36c4c6f1..6e002ec3b5a799 100644 --- a/Lib/test/test_asyncio/test_events.py +++ b/Lib/test/test_asyncio/test_events.py @@ -2698,18 +2698,18 @@ def get_event_loop(self): asyncio.set_event_loop_policy(Policy()) loop = asyncio.new_event_loop() - with self.assertRaises(TestError): - with self.assertWarns(DeprecationWarning) as cm: + 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): - with self.assertWarns(DeprecationWarning) as cm: + 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(): @@ -2720,14 +2720,14 @@ async def func(): loop.run_until_complete(func()) asyncio.set_event_loop(loop) - with self.assertRaises(TestError): - with self.assertWarns(DeprecationWarning) as cm: + 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): - with self.assertWarns(DeprecationWarning) as cm: + with self.assertWarns(DeprecationWarning) as cm: + with self.assertRaises(TestError): asyncio.get_event_loop() self.assertEqual(cm.warnings[0].filename, __file__) @@ -2737,7 +2737,56 @@ async def func(): 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) diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py index 4d2df6d203edd2..1750a2fff487f2 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -102,13 +102,7 @@ def test_ensure_future(self): class BaseFutureTests: def _new_future(self, *args, **kwargs): - if kwargs.get('loop') is None: - with self.assertWarns(DeprecationWarning) as cm: - fut = self.cls(*args, **kwargs) - self.assertEqual(cm.warnings[0].filename, __file__) - return fut - else: - return self.cls(*args, **kwargs) + return self.cls(*args, **kwargs) def setUp(self): super().setUp() @@ -145,9 +139,28 @@ def test_initial_state(self): f.cancel() self.assertTrue(f.cancelled()) - def test_init_constructor_default_loop(self): + def test_constructor_without_loop(self): asyncio.set_event_loop(self.loop) - f = self._new_future() + asyncio.set_event_loop(None) + with self.assertWarns(DeprecationWarning) as cm: + with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): + self._new_future() + self.assertEqual(cm.warnings[0].filename, __file__) + + def test_constructor_with_running_loop(self): + asyncio.set_event_loop(None) + async def test(): + return self._new_future() + f = self.loop.run_until_complete(test()) + self.assertIs(f._loop, self.loop) + self.assertIs(f.get_loop(), self.loop) + + def test_constructor_with_global_loop(self): + asyncio.set_event_loop(self.loop) + self.addCleanup(asyncio.set_event_loop, None) + with self.assertWarns(DeprecationWarning) as cm: + f = self._new_future() + self.assertEqual(cm.warnings[0].filename, __file__) self.assertIs(f._loop, self.loop) self.assertIs(f.get_loop(), self.loop) diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index 531ca5b76c212f..e89c360de67914 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -40,11 +40,6 @@ def tearDown(self): gc.collect() super().tearDown() - @mock.patch('asyncio.streams.events') - def test_ctor_global_loop(self, m_events): - stream = asyncio.StreamReader() - self.assertIs(stream._loop, m_events._get_event_loop.return_value) - def _basetest_open_connection(self, open_connection_fut): messages = [] self.loop.set_exception_handler(lambda loop, ctx: messages.append(ctx)) @@ -749,23 +744,53 @@ def test_read_all_from_pipe_reader(self): data = self.loop.run_until_complete(reader.read(-1)) self.assertEqual(data, b'data') - def test_streamreader_constructor(self): - self.addCleanup(asyncio.set_event_loop, None) - asyncio.set_event_loop(self.loop) + def test_streamreader_constructor_without_loop(self): + with self.assertWarns(DeprecationWarning) as cm: + with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): + asyncio.StreamReader() + self.assertEqual(cm.warnings[0].filename, __file__) + def test_streamreader_constructor_with_running_loop(self): # asyncio issue #184: Ensure that StreamReaderProtocol constructor # retrieves the current loop if the loop parameter is not set + async def test(): + return asyncio.StreamReader() + + reader = self.loop.run_until_complete(test()) + self.assertIs(reader._loop, self.loop) + + def test_streamreader_constructor_with_global_loop(self): + # asyncio issue #184: Ensure that StreamReaderProtocol constructor + # retrieves the current loop if the loop parameter is not set + self.addCleanup(asyncio.set_event_loop, None) + asyncio.set_event_loop(self.loop) with self.assertWarns(DeprecationWarning) as cm: reader = asyncio.StreamReader() self.assertEqual(cm.warnings[0].filename, __file__) self.assertIs(reader._loop, self.loop) - def test_streamreaderprotocol_constructor(self): - self.addCleanup(asyncio.set_event_loop, None) - asyncio.set_event_loop(self.loop) + def test_streamreaderprotocol_constructor_without_loop(self): + reader = mock.Mock() + with self.assertWarns(DeprecationWarning) as cm: + with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): + asyncio.StreamReaderProtocol(reader) + self.assertEqual(cm.warnings[0].filename, __file__) + + def test_streamreaderprotocol_constructor_with_running_loop(self): + # asyncio issue #184: Ensure that StreamReaderProtocol constructor + # retrieves the current loop if the loop parameter is not set + reader = mock.Mock() + async def test(): + return asyncio.StreamReaderProtocol(reader) + protocol = self.loop.run_until_complete(test()) + self.assertIs(protocol._loop, self.loop) + + def test_streamreaderprotocol_constructor_with_global_loop(self): # asyncio issue #184: Ensure that StreamReaderProtocol constructor # retrieves the current loop if the loop parameter is not set + self.addCleanup(asyncio.set_event_loop, None) + asyncio.set_event_loop(self.loop) reader = mock.Mock() with self.assertWarns(DeprecationWarning) as cm: protocol = asyncio.StreamReaderProtocol(reader) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 4325fba9cbb827..7d8b6e04807616 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1078,33 +1078,6 @@ async def coro(): res = loop.run_until_complete(asyncio.wait_for(coro(), timeout=None)) self.assertEqual(res, 'done') - def test_wait_for_with_global_loop(self): - - def gen(): - when = yield - self.assertAlmostEqual(0.2, when) - when = yield 0 - self.assertAlmostEqual(0.01, when) - yield 0.01 - - loop = self.new_test_loop(gen) - - async def foo(): - await asyncio.sleep(0.2) - return 'done' - - asyncio.set_event_loop(loop) - try: - fut = self.new_task(loop, foo()) - with self.assertRaises(asyncio.TimeoutError): - loop.run_until_complete(asyncio.wait_for(fut, 0.01)) - finally: - asyncio.set_event_loop(None) - - self.assertAlmostEqual(0.01, loop.time()) - self.assertTrue(fut.done()) - self.assertTrue(fut.cancelled()) - def test_wait_for_race_condition(self): def gen(): @@ -1293,32 +1266,6 @@ async def foo(): self.assertAlmostEqual(0.15, loop.time()) self.assertEqual(res, 42) - def test_wait_with_global_loop(self): - - def gen(): - when = yield - self.assertAlmostEqual(0.01, when) - when = yield 0 - self.assertAlmostEqual(0.015, when) - yield 0.015 - - loop = self.new_test_loop(gen) - - a = self.new_task(loop, asyncio.sleep(0.01)) - b = self.new_task(loop, asyncio.sleep(0.015)) - - async def foo(): - done, pending = await asyncio.wait([b, a]) - self.assertEqual(done, set([a, b])) - self.assertEqual(pending, set()) - return 42 - - asyncio.set_event_loop(loop) - res = loop.run_until_complete( - self.new_task(loop, foo())) - - self.assertEqual(res, 42) - def test_wait_duplicate_coroutines(self): with self.assertWarns(DeprecationWarning): @@ -1679,24 +1626,24 @@ def gen(): yield 0 loop = self.new_test_loop(gen) - asyncio.set_event_loop(loop) a = asyncio.sleep(0.05, 'a') b = asyncio.sleep(0.10, 'b') fs = {a, b} - with self.assertWarns(DeprecationWarning) as cm: + async def test(): futs = list(asyncio.as_completed(fs)) - self.assertEqual(cm.warnings[0].filename, __file__) - self.assertEqual(len(futs), 2) + self.assertEqual(len(futs), 2) + + x = await futs[1] + self.assertEqual(x, 'a') + self.assertAlmostEqual(0.05, loop.time()) + loop.advance_time(0.05) + y = await futs[0] + self.assertEqual(y, 'b') + self.assertAlmostEqual(0.10, loop.time()) - x = loop.run_until_complete(futs[1]) - self.assertEqual(x, 'a') - self.assertAlmostEqual(0.05, loop.time()) - loop.advance_time(0.05) - y = loop.run_until_complete(futs[0]) - self.assertEqual(y, 'b') - self.assertAlmostEqual(0.10, loop.time()) + loop.run_until_complete(test()) def test_as_completed_concurrent(self): @@ -1707,22 +1654,22 @@ def gen(): self.assertAlmostEqual(0.05, when) yield 0.05 - loop = self.new_test_loop(gen) - asyncio.set_event_loop(loop) - a = asyncio.sleep(0.05, 'a') b = asyncio.sleep(0.05, 'b') fs = {a, b} - with self.assertWarns(DeprecationWarning) as cm: + async def test(): futs = list(asyncio.as_completed(fs)) - self.assertEqual(cm.warnings[0].filename, __file__) - self.assertEqual(len(futs), 2) - waiter = asyncio.wait(futs) - # Deprecation from passing coros in futs to asyncio.wait() - with self.assertWarns(DeprecationWarning): - done, pending = loop.run_until_complete(waiter) - self.assertEqual(set(f.result() for f in done), {'a', 'b'}) + self.assertEqual(len(futs), 2) + waiter = asyncio.wait(futs) + # Deprecation from passing coros in futs to asyncio.wait() + with self.assertWarns(DeprecationWarning) as cm: + done, pending = await waiter + self.assertEqual(cm.warnings[0].filename, __file__) + self.assertEqual(set(f.result() for f in done), {'a', 'b'}) + + loop = self.new_test_loop(gen) + loop.run_until_complete(test()) def test_as_completed_duplicate_coroutines(self): @@ -2511,18 +2458,17 @@ def test_cancel_gather_1(self): """Ensure that a gathering future refuses to be cancelled once all children are done""" loop = asyncio.new_event_loop() - asyncio.set_event_loop(loop) self.addCleanup(loop.close) fut = self.new_future(loop) - # The indirection fut->child_coro is needed since otherwise the - # gathering task is done at the same time as the child future - def child_coro(): - return (yield from fut) - with self.assertWarns(DeprecationWarning) as cm: + async def create(): + # The indirection fut->child_coro is needed since otherwise the + # gathering task is done at the same time as the child future + def child_coro(): + return (yield from fut) gather_future = asyncio.gather(child_coro()) - self.assertEqual(cm.warnings[0].filename, __file__) - gather_task = asyncio.ensure_future(gather_future, loop=loop) + return asyncio.ensure_future(gather_future) + gather_task = loop.run_until_complete(create()) cancel_result = None def cancelling_callback(_): @@ -3228,12 +3174,7 @@ def _run_loop(self, loop): def _check_success(self, **kwargs): a, b, c = [self.one_loop.create_future() for i in range(3)] - if self.is_future: - fut = asyncio.gather(*self.wrap_futures(a, b, c), **kwargs) - else: - with self.assertWarns(DeprecationWarning) as cm: - fut = asyncio.gather(*self.wrap_futures(a, b, c), **kwargs) - self.assertEqual(cm.warnings[0].filename, __file__) + fut = self._gather(*self.wrap_futures(a, b, c), **kwargs) cb = test_utils.MockCallback() fut.add_done_callback(cb) b.set_result(1) @@ -3255,12 +3196,7 @@ def test_result_exception_success(self): def test_one_exception(self): a, b, c, d, e = [self.one_loop.create_future() for i in range(5)] - if self.is_future: - fut = asyncio.gather(*self.wrap_futures(a, b, c, d, e)) - else: - with self.assertWarns(DeprecationWarning) as cm: - fut = asyncio.gather(*self.wrap_futures(a, b, c, d, e)) - self.assertEqual(cm.warnings[0].filename, __file__) + fut = self._gather(*self.wrap_futures(a, b, c, d, e)) cb = test_utils.MockCallback() fut.add_done_callback(cb) exc = ZeroDivisionError() @@ -3278,14 +3214,8 @@ def test_one_exception(self): def test_return_exceptions(self): a, b, c, d = [self.one_loop.create_future() for i in range(4)] - if self.is_future: - fut = asyncio.gather(*self.wrap_futures(a, b, c, d), - return_exceptions=True) - else: - with self.assertWarns(DeprecationWarning) as cm: - fut = asyncio.gather(*self.wrap_futures(a, b, c, d), - return_exceptions=True) - self.assertEqual(cm.warnings[0].filename, __file__) + fut = self._gather(*self.wrap_futures(a, b, c, d), + return_exceptions=True) cb = test_utils.MockCallback() fut.add_done_callback(cb) exc = ZeroDivisionError() @@ -3334,14 +3264,41 @@ def test_env_var_debug(self): class FutureGatherTests(GatherTestsBase, test_utils.TestCase): - is_future = True - def wrap_futures(self, *futures): return futures - def _check_empty_sequence(self, seq_or_iter): - asyncio.set_event_loop(self.one_loop) - self.addCleanup(asyncio.set_event_loop, None) + def _gather(self, *args, **kwargs): + return asyncio.gather(*args, **kwargs) + + def _check_empty_sequence_without_loop(self, seq_or_iter): + with self.assertWarns(DeprecationWarning) as cm: + with self.assertRaises(RuntimeError): + asyncio.gather(*seq_or_iter) + self.assertEqual(cm.warnings[0].filename, __file__) + + def test_constructor_empty_sequence_without_loop(self): + self._check_empty_sequence_without_loop([]) + self._check_empty_sequence_without_loop(()) + self._check_empty_sequence_without_loop(set()) + self._check_empty_sequence_without_loop(iter("")) + + def _check_empty_sequence_with_running_loop(self, seq_or_iter): + async def gather(): + return asyncio.gather(*seq_or_iter) + fut = self.one_loop.run_until_complete(gather()) + self.assertIsInstance(fut, asyncio.Future) + self.assertIs(fut._loop, self.one_loop) + self._run_loop(self.one_loop) + self.assertTrue(fut.done()) + self.assertEqual(fut.result(), []) + + def test_constructor_empty_sequence_with_running_loop(self): + self._check_empty_sequence_with_running_loop([]) + self._check_empty_sequence_with_running_loop(()) + self._check_empty_sequence_with_running_loop(set()) + self._check_empty_sequence_with_running_loop(iter("")) + + def _check_empty_sequence_with_global_loop(self, seq_or_iter): with self.assertWarns(DeprecationWarning) as cm: fut = asyncio.gather(*seq_or_iter) self.assertEqual(cm.warnings[0].filename, __file__) @@ -3351,11 +3308,15 @@ def _check_empty_sequence(self, seq_or_iter): self.assertTrue(fut.done()) self.assertEqual(fut.result(), []) - def test_constructor_empty_sequence(self): - self._check_empty_sequence([]) - self._check_empty_sequence(()) - self._check_empty_sequence(set()) - self._check_empty_sequence(iter("")) + def test_constructor_empty_sequence_with_global_loop(self): + try: + asyncio.set_event_loop(self.one_loop) + self._check_empty_sequence_with_global_loop([]) + self._check_empty_sequence_with_global_loop(()) + self._check_empty_sequence_with_global_loop(set()) + self._check_empty_sequence_with_global_loop(iter("")) + finally: + asyncio.set_event_loop(None) def test_constructor_heterogenous_futures(self): fut1 = self.one_loop.create_future() @@ -3418,12 +3379,6 @@ def test_result_exception_one_cancellation(self): class CoroutineGatherTests(GatherTestsBase, test_utils.TestCase): - is_future = False - - def setUp(self): - super().setUp() - asyncio.set_event_loop(self.one_loop) - def wrap_futures(self, *futures): coros = [] for fut in futures: @@ -3432,26 +3387,45 @@ async def coro(fut=fut): coros.append(coro()) return coros - def test_constructor_loop_selection(self): + def _gather(self, *args, **kwargs): + async def coro(): + return asyncio.gather(*args, **kwargs) + return self.one_loop.run_until_complete(coro()) + + def test_constructor_without_loop(self): async def coro(): return 'abc' gen1 = coro() + self.addCleanup(gen1.close) gen2 = coro() + self.addCleanup(gen2.close) with self.assertWarns(DeprecationWarning) as cm: - fut = asyncio.gather(gen1, gen2) + with self.assertRaises(RuntimeError): + asyncio.gather(gen1, gen2) self.assertEqual(cm.warnings[0].filename, __file__) + + def test_constructor_with_running_loop(self): + async def coro(): + return 'abc' + gen1 = coro() + gen2 = coro() + async def gather(): + return asyncio.gather(gen1, gen2) + fut = self.one_loop.run_until_complete(gather()) self.assertIs(fut._loop, self.one_loop) self.one_loop.run_until_complete(fut) - self.set_event_loop(self.other_loop, cleanup=False) + def test_constructor_with_global_loop(self): + async def coro(): + return 'abc' asyncio.set_event_loop(self.other_loop) - gen3 = coro() - gen4 = coro() + gen1 = coro() + gen2 = coro() with self.assertWarns(DeprecationWarning) as cm: - fut2 = asyncio.gather(gen3, gen4) + fut = asyncio.gather(gen1, gen2) self.assertEqual(cm.warnings[0].filename, __file__) - self.assertIs(fut2._loop, self.other_loop) - self.other_loop.run_until_complete(fut2) + self.assertIs(fut._loop, self.other_loop) + self.other_loop.run_until_complete(fut) def test_duplicate_coroutines(self): with self.assertWarns(DeprecationWarning): @@ -3459,9 +3433,7 @@ def test_duplicate_coroutines(self): def coro(s): return s c = coro('abc') - with self.assertWarns(DeprecationWarning) as cm: - fut = asyncio.gather(c, c, coro('def'), c) - self.assertEqual(cm.warnings[0].filename, __file__) + fut = self._gather(c, c, coro('def'), c) self._run_loop(self.one_loop) self.assertEqual(fut.result(), ['abc', 'abc', 'def', 'abc']) diff --git a/Modules/_asynciomodule.c b/Modules/_asynciomodule.c index b8c672f6cf5609..a4d5d4551e9b0a 100644 --- a/Modules/_asynciomodule.c +++ b/Modules/_asynciomodule.c @@ -332,7 +332,7 @@ get_event_loop(int stacklevel) } if (PyErr_WarnEx(PyExc_DeprecationWarning, - "There is no running event loop set", + "There is no current event loop", stacklevel)) { return NULL; From b6c95466e761b7841fef02587a46fbee904b50e0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 6 Dec 2020 17:16:08 +0200 Subject: [PATCH 3/9] Add more tests. --- Lib/asyncio/streams.py | 8 +- Lib/asyncio/subprocess.py | 4 +- Lib/asyncio/tasks.py | 3 +- Lib/test/test_asyncio/test_futures.py | 48 +++++--- Lib/test/test_asyncio/test_streams.py | 2 + Lib/test/test_asyncio/test_tasks.py | 160 +++++++++++++++++++++++--- 6 files changed, 191 insertions(+), 34 deletions(-) diff --git a/Lib/asyncio/streams.py b/Lib/asyncio/streams.py index de2bf8472af9f1..080d8a62cde1e2 100644 --- a/Lib/asyncio/streams.py +++ b/Lib/asyncio/streams.py @@ -41,7 +41,7 @@ async def open_connection(host=None, port=None, *, StreamReaderProtocol classes, just copy the code -- there's really nothing special here except some convenience.) """ - loop = events._get_event_loop() + loop = events.get_running_loop() reader = StreamReader(limit=limit, loop=loop) protocol = StreamReaderProtocol(reader, loop=loop) transport, _ = await loop.create_connection( @@ -73,7 +73,7 @@ async def start_server(client_connected_cb, host=None, port=None, *, The return value is the same as loop.create_server(), i.e. a Server object which can be used to stop the service. """ - loop = events._get_event_loop() + loop = events.get_running_loop() def factory(): reader = StreamReader(limit=limit, loop=loop) @@ -90,7 +90,7 @@ def factory(): async def open_unix_connection(path=None, *, limit=_DEFAULT_LIMIT, **kwds): """Similar to `open_connection` but works with UNIX Domain Sockets.""" - loop = events._get_event_loop() + loop = events.get_running_loop() reader = StreamReader(limit=limit, loop=loop) protocol = StreamReaderProtocol(reader, loop=loop) @@ -102,7 +102,7 @@ async def open_unix_connection(path=None, *, async def start_unix_server(client_connected_cb, path=None, *, limit=_DEFAULT_LIMIT, **kwds): """Similar to `start_server` but works with UNIX Domain Sockets.""" - loop = events._get_event_loop() + loop = events.get_running_loop() def factory(): reader = StreamReader(limit=limit, loop=loop) diff --git a/Lib/asyncio/subprocess.py b/Lib/asyncio/subprocess.py index 92483595307620..cd10231f710f11 100644 --- a/Lib/asyncio/subprocess.py +++ b/Lib/asyncio/subprocess.py @@ -199,7 +199,7 @@ async def communicate(self, input=None): async def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None, limit=streams._DEFAULT_LIMIT, **kwds): - loop = events._get_event_loop() + loop = events.get_running_loop() protocol_factory = lambda: SubprocessStreamProtocol(limit=limit, loop=loop) transport, protocol = await loop.subprocess_shell( @@ -212,7 +212,7 @@ async def create_subprocess_shell(cmd, stdin=None, stdout=None, stderr=None, async def create_subprocess_exec(program, *args, stdin=None, stdout=None, stderr=None, limit=streams._DEFAULT_LIMIT, **kwds): - loop = events._get_event_loop() + loop = events.get_running_loop() protocol_factory = lambda: SubprocessStreamProtocol(limit=limit, loop=loop) transport, protocol = await loop.subprocess_exec( diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 9e34538c7c6103..dfa26879bc1d67 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -681,7 +681,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 diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py index 1750a2fff487f2..15ba7944765518 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -140,15 +140,12 @@ def test_initial_state(self): self.assertTrue(f.cancelled()) def test_constructor_without_loop(self): - asyncio.set_event_loop(self.loop) - asyncio.set_event_loop(None) with self.assertWarns(DeprecationWarning) as cm: with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): self._new_future() self.assertEqual(cm.warnings[0].filename, __file__) def test_constructor_with_running_loop(self): - asyncio.set_event_loop(None) async def test(): return self._new_future() f = self.loop.run_until_complete(test()) @@ -156,6 +153,7 @@ async def test(): self.assertIs(f.get_loop(), self.loop) def test_constructor_with_global_loop(self): + # Deprecated in 3.10 asyncio.set_event_loop(self.loop) self.addCleanup(asyncio.set_event_loop, None) with self.assertWarns(DeprecationWarning) as cm: @@ -491,17 +489,41 @@ def test_wrap_future_future(self): f2 = asyncio.wrap_future(f1) self.assertIs(f1, f2) - def test_wrap_future_use_global_loop(self): - with mock.patch('asyncio.futures.events') as events: - events.get_event_loop = lambda: self.loop - events._get_event_loop = lambda: self.loop - def run(arg): - return (arg, threading.get_ident()) - ex = concurrent.futures.ThreadPoolExecutor(1) - f1 = ex.submit(run, 'oi') + def test_wrap_future_without_loop(self): + def run(arg): + return (arg, threading.get_ident()) + ex = concurrent.futures.ThreadPoolExecutor(1) + f1 = ex.submit(run, 'oi') + with self.assertWarns(DeprecationWarning) as cm: + with self.assertRaises(RuntimeError): + asyncio.wrap_future(f1) + self.assertEqual(cm.warnings[0].filename, __file__) + ex.shutdown(wait=True) + + def test_wrap_future_with_running_loop(self): + def run(arg): + return (arg, threading.get_ident()) + ex = concurrent.futures.ThreadPoolExecutor(1) + f1 = ex.submit(run, 'oi') + async def test(): + return asyncio.wrap_future(f1) + f2 = self.loop.run_until_complete(test()) + self.assertIs(self.loop, f2._loop) + ex.shutdown(wait=True) + + def test_wrap_future_with_global_loop(self): + # Deprecated in 3.10 + asyncio.set_event_loop(self.loop) + self.addCleanup(asyncio.set_event_loop, None) + def run(arg): + return (arg, threading.get_ident()) + ex = concurrent.futures.ThreadPoolExecutor(1) + f1 = ex.submit(run, 'oi') + with self.assertWarns(DeprecationWarning) as cm: f2 = asyncio.wrap_future(f1) - self.assertIs(self.loop, f2._loop) - ex.shutdown(wait=True) + self.assertEqual(cm.warnings[0].filename, __file__) + self.assertIs(self.loop, f2._loop) + ex.shutdown(wait=True) def test_wrap_future_cancel(self): f1 = concurrent.futures.Future() diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index e89c360de67914..656a221d7a4c7d 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -762,6 +762,7 @@ async def test(): def test_streamreader_constructor_with_global_loop(self): # asyncio issue #184: Ensure that StreamReaderProtocol constructor # retrieves the current loop if the loop parameter is not set + # Deprecated in 3.10 self.addCleanup(asyncio.set_event_loop, None) asyncio.set_event_loop(self.loop) with self.assertWarns(DeprecationWarning) as cm: @@ -789,6 +790,7 @@ async def test(): def test_streamreaderprotocol_constructor_with_global_loop(self): # asyncio issue #184: Ensure that StreamReaderProtocol constructor # retrieves the current loop if the loop parameter is not set + # Deprecated in 3.10 self.addCleanup(asyncio.set_event_loop, None) asyncio.set_event_loop(self.loop) reader = mock.Mock() diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 7d8b6e04807616..2e24ac971871b3 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -200,22 +200,76 @@ async def notmuch(): loop.close() def test_ensure_future_coroutine(self): + async def notmuch(): + return 'ok' + t = asyncio.ensure_future(notmuch(), loop=self.loop) + self.assertIs(t._loop, self.loop) + self.loop.run_until_complete(t) + self.assertTrue(t.done()) + self.assertEqual(t.result(), 'ok') + + a = notmuch() + self.addCleanup(a.close) + with self.assertWarns(DeprecationWarning) as cm: + with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): + asyncio.ensure_future(a) + self.assertEqual(cm.warnings[0].filename, __file__) + + async def test(): + return asyncio.ensure_future(notmuch()) + t = self.loop.run_until_complete(test()) + self.assertIs(t._loop, self.loop) + self.loop.run_until_complete(t) + self.assertTrue(t.done()) + self.assertEqual(t.result(), 'ok') + + # Deprecated in 3.10 + asyncio.set_event_loop(self.loop) + self.addCleanup(asyncio.set_event_loop, None) + with self.assertWarns(DeprecationWarning) as cm: + t = asyncio.ensure_future(notmuch()) + self.assertEqual(cm.warnings[0].filename, __file__) + self.assertIs(t._loop, self.loop) + self.loop.run_until_complete(t) + self.assertTrue(t.done()) + self.assertEqual(t.result(), 'ok') + + def test_ensure_future_coroutine_2(self): with self.assertWarns(DeprecationWarning): @asyncio.coroutine def notmuch(): return 'ok' t = asyncio.ensure_future(notmuch(), loop=self.loop) + self.assertIs(t._loop, self.loop) self.loop.run_until_complete(t) self.assertTrue(t.done()) self.assertEqual(t.result(), 'ok') + + a = notmuch() + self.addCleanup(a.close) + with self.assertWarns(DeprecationWarning) as cm: + with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): + asyncio.ensure_future(a) + self.assertEqual(cm.warnings[0].filename, __file__) + + async def test(): + return asyncio.ensure_future(notmuch()) + t = self.loop.run_until_complete(test()) self.assertIs(t._loop, self.loop) + self.loop.run_until_complete(t) + self.assertTrue(t.done()) + self.assertEqual(t.result(), 'ok') - loop = asyncio.new_event_loop() - self.set_event_loop(loop) - t = asyncio.ensure_future(notmuch(), loop=loop) - self.assertIs(t._loop, loop) - loop.run_until_complete(t) - loop.close() + # Deprecated in 3.10 + asyncio.set_event_loop(self.loop) + self.addCleanup(asyncio.set_event_loop, None) + with self.assertWarns(DeprecationWarning) as cm: + t = asyncio.ensure_future(notmuch()) + self.assertEqual(cm.warnings[0].filename, __file__) + self.assertIs(t._loop, self.loop) + self.loop.run_until_complete(t) + self.assertTrue(t.done()) + self.assertEqual(t.result(), 'ok') def test_ensure_future_future(self): f_orig = self.new_future(self.loop) @@ -1693,6 +1747,47 @@ def runner(): self.assertEqual(set(result), {'ham', 'spam'}) self.assertEqual(len(result), 2) + def test_as_completed_nontask_without_loop(self): + async def coro(): + return 42 + + a = coro() + self.addCleanup(a.close) + + futs = asyncio.as_completed([a]) + with self.assertWarns(DeprecationWarning) as cm: + with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): + list(futs) + self.assertEqual(cm.warnings[0].filename, __file__) + + def test_as_completed_nontask_with_running_loop(self): + loop = self.new_test_loop() + + async def coro(): + return 42 + + async def test(): + futs = list(asyncio.as_completed([coro()])) + self.assertEqual(len(futs), 1) + self.assertEqual(await futs[0], 42) + + loop.run_until_complete(test()) + + def test_as_completed_nontask_with_global_loop(self): + # Deprecated in 3.10 + async def coro(): + return 42 + + loop = self.new_test_loop() + asyncio.set_event_loop(loop) + self.addCleanup(asyncio.set_event_loop, None) + futs = asyncio.as_completed([coro()]) + with self.assertWarns(DeprecationWarning) as cm: + futs = list(futs) + self.assertEqual(cm.warnings[0].filename, __file__) + self.assertEqual(len(futs), 1) + self.assertEqual(loop.run_until_complete(futs[0]), 42) + def test_sleep(self): def gen(): @@ -2152,6 +2247,42 @@ def test_gather_shield(self): child2.set_result(2) test_utils.run_briefly(self.loop) + def test_shield_coroutine_without_loop(self): + async def coro(): + return 42 + + inner = coro() + self.addCleanup(inner.close) + with self.assertWarns(DeprecationWarning) as cm: + with self.assertRaisesRegex(RuntimeError, 'There is no current event loop'): + asyncio.shield(inner) + self.assertEqual(cm.warnings[0].filename, __file__) + + def test_shield_coroutine_with_running_loop(self): + async def coro(): + return 42 + + async def test(): + return asyncio.shield(coro()) + outer = self.loop.run_until_complete(test()) + self.assertEqual(outer._loop, self.loop) + res = self.loop.run_until_complete(outer) + self.assertEqual(res, 42) + + def test_shield_coroutine_with_global_loop(self): + # Deprecated in 3.10 + async def coro(): + return 42 + + asyncio.set_event_loop(self.loop) + self.addCleanup(asyncio.set_event_loop, None) + with self.assertWarns(DeprecationWarning) as cm: + outer = asyncio.shield(coro()) + self.assertEqual(cm.warnings[0].filename, __file__) + self.assertEqual(outer._loop, self.loop) + res = self.loop.run_until_complete(outer) + self.assertEqual(res, 42) + def test_as_completed_invalid_args(self): fut = self.new_future(self.loop) @@ -3309,14 +3440,13 @@ def _check_empty_sequence_with_global_loop(self, seq_or_iter): self.assertEqual(fut.result(), []) def test_constructor_empty_sequence_with_global_loop(self): - try: - asyncio.set_event_loop(self.one_loop) - self._check_empty_sequence_with_global_loop([]) - self._check_empty_sequence_with_global_loop(()) - self._check_empty_sequence_with_global_loop(set()) - self._check_empty_sequence_with_global_loop(iter("")) - finally: - asyncio.set_event_loop(None) + # Deprecated in 3.10 + asyncio.set_event_loop(self.one_loop) + self.addCleanup(asyncio.set_event_loop, None) + self._check_empty_sequence_with_global_loop([]) + self._check_empty_sequence_with_global_loop(()) + self._check_empty_sequence_with_global_loop(set()) + self._check_empty_sequence_with_global_loop(iter("")) def test_constructor_heterogenous_futures(self): fut1 = self.one_loop.create_future() @@ -3416,9 +3546,11 @@ async def gather(): self.one_loop.run_until_complete(fut) def test_constructor_with_global_loop(self): + # Deprecated in 3.10 async def coro(): return 'abc' asyncio.set_event_loop(self.other_loop) + self.addCleanup(asyncio.set_event_loop, None) gen1 = coro() gen2 = coro() with self.assertWarns(DeprecationWarning) as cm: From f972573e220708edec7bf58f1e3a0c350c97fd07 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 6 Dec 2020 17:57:37 +0200 Subject: [PATCH 4/9] Revert some temporary changes. --- Lib/asyncio/futures.py | 2 +- Lib/asyncio/tasks.py | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/asyncio/futures.py b/Lib/asyncio/futures.py index 48b927ada74811..10f8f0554e4b60 100644 --- a/Lib/asyncio/futures.py +++ b/Lib/asyncio/futures.py @@ -371,7 +371,7 @@ def _chain_future(source, destination): raise TypeError('A future is required for source argument') if not isfuture(destination) and not isinstance(destination, concurrent.futures.Future): - raise TypeError('A future is required for destination argument %r' % destination) + raise TypeError('A future is required for destination argument') source_loop = _get_loop(source) if isfuture(source) else None dest_loop = _get_loop(destination) if isfuture(destination) else None diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index dfa26879bc1d67..9a05c9b7fdafdb 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -66,7 +66,7 @@ def _all_tasks_compat(loop=None): # the completed ones. Used to implement deprecated "Tasks.all_task()" # method. if loop is None: - loop = events._get_event_loop() + loop = events.get_event_loop() # Looping over a WeakSet (_all_tasks) isn't safe as it can be updated from another # thread while we do so. Therefore we cast it to list prior to filtering. The list # cast itself requires iteration, so we repeat it several times ignoring From fba117d78ff138efc5706273269a85f7ae43b0c6 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 6 Dec 2020 18:52:44 +0200 Subject: [PATCH 5/9] Refactor _ensure_future(). --- Lib/asyncio/tasks.py | 30 ++++++++++++++---------------- 1 file changed, 14 insertions(+), 16 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 9a05c9b7fdafdb..5493d66daab504 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -641,24 +641,22 @@ def ensure_future(coro_or_future, *, loop=None): def _ensure_future(coro_or_future, *, loop=None): - while True: - if coroutines.iscoroutine(coro_or_future): - if loop is None: - loop = events._get_event_loop(stacklevel=4) - task = loop.create_task(coro_or_future) - if task._source_traceback: - del task._source_traceback[-1] - return task - elif 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') - return coro_or_future - elif inspect.isawaitable(coro_or_future): + 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') + return coro_or_future + + 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') + 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 From fe6a4f6d99380a2fbe24652e8003b89c892970e5 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 6 Dec 2020 19:02:00 +0200 Subject: [PATCH 6/9] Rename tests. --- Lib/test/test_asyncio/test_futures.py | 8 +++--- Lib/test/test_asyncio/test_streams.py | 8 +++--- Lib/test/test_asyncio/test_tasks.py | 36 +++++++++++++-------------- 3 files changed, 26 insertions(+), 26 deletions(-) diff --git a/Lib/test/test_asyncio/test_futures.py b/Lib/test/test_asyncio/test_futures.py index 15ba7944765518..fe3d44227c83cc 100644 --- a/Lib/test/test_asyncio/test_futures.py +++ b/Lib/test/test_asyncio/test_futures.py @@ -145,14 +145,14 @@ def test_constructor_without_loop(self): self._new_future() self.assertEqual(cm.warnings[0].filename, __file__) - def test_constructor_with_running_loop(self): + def test_constructor_use_running_loop(self): async def test(): return self._new_future() f = self.loop.run_until_complete(test()) self.assertIs(f._loop, self.loop) self.assertIs(f.get_loop(), self.loop) - def test_constructor_with_global_loop(self): + def test_constructor_use_global_loop(self): # Deprecated in 3.10 asyncio.set_event_loop(self.loop) self.addCleanup(asyncio.set_event_loop, None) @@ -500,7 +500,7 @@ def run(arg): self.assertEqual(cm.warnings[0].filename, __file__) ex.shutdown(wait=True) - def test_wrap_future_with_running_loop(self): + def test_wrap_future_use_running_loop(self): def run(arg): return (arg, threading.get_ident()) ex = concurrent.futures.ThreadPoolExecutor(1) @@ -511,7 +511,7 @@ async def test(): self.assertIs(self.loop, f2._loop) ex.shutdown(wait=True) - def test_wrap_future_with_global_loop(self): + def test_wrap_future_use_global_loop(self): # Deprecated in 3.10 asyncio.set_event_loop(self.loop) self.addCleanup(asyncio.set_event_loop, None) diff --git a/Lib/test/test_asyncio/test_streams.py b/Lib/test/test_asyncio/test_streams.py index 2d6865b70d9589..9b4dbe5f1fd3f8 100644 --- a/Lib/test/test_asyncio/test_streams.py +++ b/Lib/test/test_asyncio/test_streams.py @@ -752,7 +752,7 @@ def test_streamreader_constructor_without_loop(self): asyncio.StreamReader() self.assertEqual(cm.warnings[0].filename, __file__) - def test_streamreader_constructor_with_running_loop(self): + def test_streamreader_constructor_use_running_loop(self): # asyncio issue #184: Ensure that StreamReaderProtocol constructor # retrieves the current loop if the loop parameter is not set async def test(): @@ -761,7 +761,7 @@ async def test(): reader = self.loop.run_until_complete(test()) self.assertIs(reader._loop, self.loop) - def test_streamreader_constructor_with_global_loop(self): + def test_streamreader_constructor_use_global_loop(self): # asyncio issue #184: Ensure that StreamReaderProtocol constructor # retrieves the current loop if the loop parameter is not set # Deprecated in 3.10 @@ -780,7 +780,7 @@ def test_streamreaderprotocol_constructor_without_loop(self): asyncio.StreamReaderProtocol(reader) self.assertEqual(cm.warnings[0].filename, __file__) - def test_streamreaderprotocol_constructor_with_running_loop(self): + def test_streamreaderprotocol_constructor_use_running_loop(self): # asyncio issue #184: Ensure that StreamReaderProtocol constructor # retrieves the current loop if the loop parameter is not set reader = mock.Mock() @@ -789,7 +789,7 @@ async def test(): protocol = self.loop.run_until_complete(test()) self.assertIs(protocol._loop, self.loop) - def test_streamreaderprotocol_constructor_with_global_loop(self): + def test_streamreaderprotocol_constructor_use_global_loop(self): # asyncio issue #184: Ensure that StreamReaderProtocol constructor # retrieves the current loop if the loop parameter is not set # Deprecated in 3.10 diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 2e24ac971871b3..300f7f7a0300c4 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1760,7 +1760,7 @@ async def coro(): list(futs) self.assertEqual(cm.warnings[0].filename, __file__) - def test_as_completed_nontask_with_running_loop(self): + def test_as_completed_nontask_use_running_loop(self): loop = self.new_test_loop() async def coro(): @@ -1773,7 +1773,7 @@ async def test(): loop.run_until_complete(test()) - def test_as_completed_nontask_with_global_loop(self): + def test_as_completed_nontask_use_global_loop(self): # Deprecated in 3.10 async def coro(): return 42 @@ -2258,7 +2258,7 @@ async def coro(): asyncio.shield(inner) self.assertEqual(cm.warnings[0].filename, __file__) - def test_shield_coroutine_with_running_loop(self): + def test_shield_coroutine_use_running_loop(self): async def coro(): return 42 @@ -2269,7 +2269,7 @@ async def test(): res = self.loop.run_until_complete(outer) self.assertEqual(res, 42) - def test_shield_coroutine_with_global_loop(self): + def test_shield_coroutine_use_global_loop(self): # Deprecated in 3.10 async def coro(): return 42 @@ -3413,7 +3413,7 @@ def test_constructor_empty_sequence_without_loop(self): self._check_empty_sequence_without_loop(set()) self._check_empty_sequence_without_loop(iter("")) - def _check_empty_sequence_with_running_loop(self, seq_or_iter): + def _check_empty_sequence_use_running_loop(self, seq_or_iter): async def gather(): return asyncio.gather(*seq_or_iter) fut = self.one_loop.run_until_complete(gather()) @@ -3423,13 +3423,13 @@ async def gather(): self.assertTrue(fut.done()) self.assertEqual(fut.result(), []) - def test_constructor_empty_sequence_with_running_loop(self): - self._check_empty_sequence_with_running_loop([]) - self._check_empty_sequence_with_running_loop(()) - self._check_empty_sequence_with_running_loop(set()) - self._check_empty_sequence_with_running_loop(iter("")) + def test_constructor_empty_sequence_use_running_loop(self): + self._check_empty_sequence_use_running_loop([]) + self._check_empty_sequence_use_running_loop(()) + self._check_empty_sequence_use_running_loop(set()) + self._check_empty_sequence_use_running_loop(iter("")) - def _check_empty_sequence_with_global_loop(self, seq_or_iter): + def _check_empty_sequence_use_global_loop(self, seq_or_iter): with self.assertWarns(DeprecationWarning) as cm: fut = asyncio.gather(*seq_or_iter) self.assertEqual(cm.warnings[0].filename, __file__) @@ -3439,14 +3439,14 @@ def _check_empty_sequence_with_global_loop(self, seq_or_iter): self.assertTrue(fut.done()) self.assertEqual(fut.result(), []) - def test_constructor_empty_sequence_with_global_loop(self): + def test_constructor_empty_sequence_use_global_loop(self): # Deprecated in 3.10 asyncio.set_event_loop(self.one_loop) self.addCleanup(asyncio.set_event_loop, None) - self._check_empty_sequence_with_global_loop([]) - self._check_empty_sequence_with_global_loop(()) - self._check_empty_sequence_with_global_loop(set()) - self._check_empty_sequence_with_global_loop(iter("")) + self._check_empty_sequence_use_global_loop([]) + self._check_empty_sequence_use_global_loop(()) + self._check_empty_sequence_use_global_loop(set()) + self._check_empty_sequence_use_global_loop(iter("")) def test_constructor_heterogenous_futures(self): fut1 = self.one_loop.create_future() @@ -3534,7 +3534,7 @@ async def coro(): asyncio.gather(gen1, gen2) self.assertEqual(cm.warnings[0].filename, __file__) - def test_constructor_with_running_loop(self): + def test_constructor_use_running_loop(self): async def coro(): return 'abc' gen1 = coro() @@ -3545,7 +3545,7 @@ async def gather(): self.assertIs(fut._loop, self.one_loop) self.one_loop.run_until_complete(fut) - def test_constructor_with_global_loop(self): + def test_constructor_use_global_loop(self): # Deprecated in 3.10 async def coro(): return 'abc' From 1eb0c5dda99ec9513f35c90d00f218dda3f121c4 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 6 Dec 2020 19:07:46 +0200 Subject: [PATCH 7/9] Simplify tests for gather() without arguments. --- Lib/test/test_asyncio/test_tasks.py | 36 ++++++++--------------------- 1 file changed, 9 insertions(+), 27 deletions(-) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 300f7f7a0300c4..c1ea214f76940d 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -3401,21 +3401,15 @@ def wrap_futures(self, *futures): def _gather(self, *args, **kwargs): return asyncio.gather(*args, **kwargs) - def _check_empty_sequence_without_loop(self, seq_or_iter): + def test_constructor_empty_sequence_without_loop(self): with self.assertWarns(DeprecationWarning) as cm: with self.assertRaises(RuntimeError): - asyncio.gather(*seq_or_iter) + asyncio.gather() self.assertEqual(cm.warnings[0].filename, __file__) - def test_constructor_empty_sequence_without_loop(self): - self._check_empty_sequence_without_loop([]) - self._check_empty_sequence_without_loop(()) - self._check_empty_sequence_without_loop(set()) - self._check_empty_sequence_without_loop(iter("")) - - def _check_empty_sequence_use_running_loop(self, seq_or_iter): + def test_constructor_empty_sequence_use_running_loop(self): async def gather(): - return asyncio.gather(*seq_or_iter) + return asyncio.gather() fut = self.one_loop.run_until_complete(gather()) self.assertIsInstance(fut, asyncio.Future) self.assertIs(fut._loop, self.one_loop) @@ -3423,15 +3417,12 @@ async def gather(): self.assertTrue(fut.done()) self.assertEqual(fut.result(), []) - def test_constructor_empty_sequence_use_running_loop(self): - self._check_empty_sequence_use_running_loop([]) - self._check_empty_sequence_use_running_loop(()) - self._check_empty_sequence_use_running_loop(set()) - self._check_empty_sequence_use_running_loop(iter("")) - - def _check_empty_sequence_use_global_loop(self, seq_or_iter): + def test_constructor_empty_sequence_use_global_loop(self): + # Deprecated in 3.10 + asyncio.set_event_loop(self.one_loop) + self.addCleanup(asyncio.set_event_loop, None) with self.assertWarns(DeprecationWarning) as cm: - fut = asyncio.gather(*seq_or_iter) + fut = asyncio.gather() self.assertEqual(cm.warnings[0].filename, __file__) self.assertIsInstance(fut, asyncio.Future) self.assertIs(fut._loop, self.one_loop) @@ -3439,15 +3430,6 @@ def _check_empty_sequence_use_global_loop(self, seq_or_iter): self.assertTrue(fut.done()) self.assertEqual(fut.result(), []) - def test_constructor_empty_sequence_use_global_loop(self): - # Deprecated in 3.10 - asyncio.set_event_loop(self.one_loop) - self.addCleanup(asyncio.set_event_loop, None) - self._check_empty_sequence_use_global_loop([]) - self._check_empty_sequence_use_global_loop(()) - self._check_empty_sequence_use_global_loop(set()) - self._check_empty_sequence_use_global_loop(iter("")) - def test_constructor_heterogenous_futures(self): fut1 = self.one_loop.create_future() fut2 = self.other_loop.create_future() From b717f71a4a9c2d6cd99656ca708f24b7c1b094c0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 6 Dec 2020 20:21:21 +0200 Subject: [PATCH 8/9] Add documentation. --- Doc/library/asyncio-eventloop.rst | 5 +++++ Doc/library/asyncio-future.rst | 12 ++++++++++++ Doc/library/asyncio-task.rst | 17 +++++++++++++++++ Doc/whatsnew/3.10.rst | 13 +++++++++++++ .../2020-12-06-20-21-16.bpo-39529.9Zrg43.rst | 9 +++++++++ 5 files changed, 56 insertions(+) create mode 100644 Misc/NEWS.d/next/Library/2020-12-06-20-21-16.bpo-39529.9Zrg43.rst diff --git a/Doc/library/asyncio-eventloop.rst b/Doc/library/asyncio-eventloop.rst index 15b5b3fe822ce8..598ca0d3515555 100644 --- a/Doc/library/asyncio-eventloop.rst +++ b/Doc/library/asyncio-eventloop.rst @@ -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. diff --git a/Doc/library/asyncio-future.rst b/Doc/library/asyncio-future.rst index e1ac18eaf09882..70faee7ad0577f 100644 --- a/Doc/library/asyncio-future.rst +++ b/Doc/library/asyncio-future.rst @@ -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 ============= @@ -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. diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst index 73ada0e2f006cd..4011153afd8577 100644 --- a/Doc/library/asyncio-task.rst +++ b/Doc/library/asyncio-task.rst @@ -393,6 +393,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 =========================== @@ -430,6 +435,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 ======== @@ -589,6 +598,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 ================== @@ -771,6 +784,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. diff --git a/Doc/whatsnew/3.10.rst b/Doc/whatsnew/3.10.rst index 019dd1817d2b63..52017449432a15 100644 --- a/Doc/whatsnew/3.10.rst +++ b/Doc/whatsnew/3.10.rst @@ -442,6 +442,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`.) + Removed ======= diff --git a/Misc/NEWS.d/next/Library/2020-12-06-20-21-16.bpo-39529.9Zrg43.rst b/Misc/NEWS.d/next/Library/2020-12-06-20-21-16.bpo-39529.9Zrg43.rst new file mode 100644 index 00000000000000..bb1fd82c99e363 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2020-12-06-20-21-16.bpo-39529.9Zrg43.rst @@ -0,0 +1,9 @@ +Deprecated use of :func:`asyncio.get_event_loop` without running event loop. +Emit deprecation warning for :mod:`asyncio` functions which implicitly +create a :class:`~asyncio.Future` or :class:`~asyncio.Task` objects 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`. From 90e78bfb322055eb501289f3a102b57e0590e8c0 Mon Sep 17 00:00:00 2001 From: Serhiy Storchaka Date: Sun, 6 Dec 2020 20:24:42 +0200 Subject: [PATCH 9/9] Rename some tests. --- Lib/test/test_asyncio/test_tasks.py | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index c1ea214f76940d..a9e4cf53566ca9 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1747,7 +1747,7 @@ def runner(): self.assertEqual(set(result), {'ham', 'spam'}) self.assertEqual(len(result), 2) - def test_as_completed_nontask_without_loop(self): + def test_as_completed_coroutine_without_loop(self): async def coro(): return 42 @@ -1760,7 +1760,7 @@ async def coro(): list(futs) self.assertEqual(cm.warnings[0].filename, __file__) - def test_as_completed_nontask_use_running_loop(self): + def test_as_completed_coroutine_use_running_loop(self): loop = self.new_test_loop() async def coro(): @@ -1773,7 +1773,7 @@ async def test(): loop.run_until_complete(test()) - def test_as_completed_nontask_use_global_loop(self): + def test_as_completed_coroutine_use_global_loop(self): # Deprecated in 3.10 async def coro(): return 42