From 9c2942afe1b08fb406acec2ae0c563c392df804f Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Fri, 3 Sep 2021 22:54:10 +0100 Subject: [PATCH 01/19] Create test for suppressing cancellations --- Lib/test/test_asyncio/test_tasks.py | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 92b1a43500311d..368988f6dcaa5e 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1023,6 +1023,24 @@ def gen(): loop.call_later(0.1, task.cancel) res = loop.run_until_complete(task) self.assertEqual(res, "ok") + + def test_wait_for_does_not_suppress_cancellation(self): + async def inner(): + return + + async def with_for_coro(): + await wait_for(inner(), timeout=1) + assert False, 'End of with_for_coro. Should not be reached!' + + async def main(self): + task = asyncio.create_task(with_for_coro()) + await asyncio.sleep(0) + self.assertFalse(task.done()) + task.cancel() + with self.assertRaises(asyncio.CancelledError): + await task + + asyncio.run(main(self)) def test_wait_for_waits_for_task_cancellation(self): loop = asyncio.new_event_loop() From 7ff2c04b3273f84591f49656b1c96bb63d430f3f Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Fri, 3 Sep 2021 23:32:51 +0100 Subject: [PATCH 02/19] Update test_tasks.py --- 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 368988f6dcaa5e..952d38887ae27b 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1026,13 +1026,13 @@ def gen(): def test_wait_for_does_not_suppress_cancellation(self): async def inner(): - return + await asyncio.sleep(0) async def with_for_coro(): await wait_for(inner(), timeout=1) assert False, 'End of with_for_coro. Should not be reached!' - async def main(self): + async def main(): task = asyncio.create_task(with_for_coro()) await asyncio.sleep(0) self.assertFalse(task.done()) @@ -1040,7 +1040,7 @@ async def main(self): with self.assertRaises(asyncio.CancelledError): await task - asyncio.run(main(self)) + asyncio.run(main()) def test_wait_for_waits_for_task_cancellation(self): loop = asyncio.new_event_loop() From cdaa9c87ff4152d0b5d2ce6c2eea85d46105dfff Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Fri, 3 Sep 2021 23:48:24 +0100 Subject: [PATCH 03/19] Update test_tasks.py --- Lib/test/test_asyncio/test_tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 952d38887ae27b..b88f8c5d74e8da 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1026,7 +1026,7 @@ def gen(): def test_wait_for_does_not_suppress_cancellation(self): async def inner(): - await asyncio.sleep(0) + await asyncio.sleep(.1) async def with_for_coro(): await wait_for(inner(), timeout=1) @@ -1034,7 +1034,7 @@ async def with_for_coro(): async def main(): task = asyncio.create_task(with_for_coro()) - await asyncio.sleep(0) + await asyncio.sleep(.1) self.assertFalse(task.done()) task.cancel() with self.assertRaises(asyncio.CancelledError): From 342222fcebfe8c6798619485bb1302597fce912c Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sat, 4 Sep 2021 00:08:45 +0100 Subject: [PATCH 04/19] Update test_tasks.py --- 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 b88f8c5d74e8da..6e111b73e08e0c 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1026,15 +1026,15 @@ def gen(): def test_wait_for_does_not_suppress_cancellation(self): async def inner(): - await asyncio.sleep(.1) + return async def with_for_coro(): - await wait_for(inner(), timeout=1) + await asyncio.wait_for(inner(), timeout=1) assert False, 'End of with_for_coro. Should not be reached!' async def main(): task = asyncio.create_task(with_for_coro()) - await asyncio.sleep(.1) + await asyncio.sleep(0) self.assertFalse(task.done()) task.cancel() with self.assertRaises(asyncio.CancelledError): From 01d988459f6020a5bd2df73946a358c07a93b74a Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sat, 4 Sep 2021 00:26:38 +0100 Subject: [PATCH 05/19] Use a sentinel --- Lib/asyncio/tasks.py | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 9a9d0d6e3cc269..b60d02318690b3 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -25,6 +25,8 @@ from . import futures from .coroutines import _is_coroutine +_SENTINEL = object() + # Helper to generate new task names # This uses itertools.count() instead of a "+= 1" operation because the latter # is not thread safe. See bpo-11866 for a longer explanation. @@ -432,8 +434,8 @@ async def wait_for(fut, timeout): # wait until the future completes or the timeout try: await waiter - except exceptions.CancelledError: - if fut.done(): + except exceptions.CancelledError as e: + if fut.done() and _SENTINEL in e.args: return fut.result() else: fut.remove_done_callback(cb) @@ -516,7 +518,7 @@ async def _cancel_and_wait(fut, loop): fut.add_done_callback(cb) try: - fut.cancel() + fut.cancel(_SENTINEL) # We cannot wait on *fut* directly to make # sure _cancel_and_wait itself is reliably cancellable. await waiter From af96f10553d835dfef8f31626987205e43b0e73b Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sat, 4 Sep 2021 15:18:59 +0100 Subject: [PATCH 06/19] Fix test for issue37658 --- Lib/test/test_asyncio/test_tasks.py | 19 +++++++------------ 1 file changed, 7 insertions(+), 12 deletions(-) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 6e111b73e08e0c..13e1466e2a97ab 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1009,20 +1009,15 @@ def gen(): self.assertEqual(res, "ok") def test_wait_for_cancellation_race_condition(self): - def gen(): - yield 0.1 - yield 0.1 - yield 0.1 - yield 0.1 + async def inner(): + await wait_for(asyncio.sleep(1), timeout=2) + return 1 - loop = self.new_test_loop(gen) + async def main(): + result = await wait_for(inner(), timeout=1) + assert result == 1 - fut = self.new_future(loop) - loop.call_later(0.1, fut.set_result, "ok") - task = loop.create_task(asyncio.wait_for(fut, timeout=1)) - loop.call_later(0.1, task.cancel) - res = loop.run_until_complete(task) - self.assertEqual(res, "ok") + asyncio.run(main()) def test_wait_for_does_not_suppress_cancellation(self): async def inner(): From 1e5233df040dcf536db533162423160bf3c66232 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sat, 4 Sep 2021 15:32:37 +0100 Subject: [PATCH 07/19] Update test_tasks.py --- Lib/test/test_asyncio/test_tasks.py | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 13e1466e2a97ab..1d61c354811435 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1010,11 +1010,11 @@ def gen(): def test_wait_for_cancellation_race_condition(self): async def inner(): - await wait_for(asyncio.sleep(1), timeout=2) + await asyncio.wait_for(asyncio.sleep(1), timeout=2) return 1 async def main(): - result = await wait_for(inner(), timeout=1) + result = await asyncio.wait_for(inner(), timeout=1) assert result == 1 asyncio.run(main()) From 743177e1102490f7a288c94b888328ac1c7ed2c1 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sat, 4 Sep 2021 15:49:35 +0100 Subject: [PATCH 08/19] Fix original issue --- Lib/asyncio/tasks.py | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index b60d02318690b3..127004bd6d8709 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -457,11 +457,9 @@ async def wait_for(fut, timeout): # exception, we should re-raise it # See https://bugs.python.org/issue40607 try: - fut.result() + return fut.result() except exceptions.CancelledError as exc: raise exceptions.TimeoutError() from exc - else: - raise exceptions.TimeoutError() finally: timeout_handle.cancel() From d7c0965bc91568bb8f531b329aa9fc8395f2021d Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Sat, 4 Sep 2021 16:07:14 +0100 Subject: [PATCH 09/19] Remove test --- Lib/test/test_asyncio/test_tasks.py | 18 ------------------ 1 file changed, 18 deletions(-) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index 1d61c354811435..0ea5152789fa24 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1114,24 +1114,6 @@ async def inner(): with self.assertRaises(FooException): loop.run_until_complete(foo()) - def test_wait_for_raises_timeout_error_if_returned_during_cancellation(self): - loop = asyncio.new_event_loop() - self.addCleanup(loop.close) - - async def foo(): - async def inner(): - try: - await asyncio.sleep(0.2) - except asyncio.CancelledError: - return 42 - - inner_task = self.new_task(loop, inner()) - - await asyncio.wait_for(inner_task, timeout=_EPSILON) - - with self.assertRaises(asyncio.TimeoutError): - loop.run_until_complete(foo()) - def test_wait_for_self_cancellation(self): loop = asyncio.new_event_loop() self.addCleanup(loop.close) From 4eb5de29b4f9ba30ebe8ee23cabcd11631b95452 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Mon, 29 Nov 2021 17:18:42 +0000 Subject: [PATCH 10/19] Create 2021-11-29-17-17-45.bpo-42130.Ho8dUh.rst --- .../NEWS.d/next/Library/2021-11-29-17-17-45.bpo-42130.Ho8dUh.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2021-11-29-17-17-45.bpo-42130.Ho8dUh.rst diff --git a/Misc/NEWS.d/next/Library/2021-11-29-17-17-45.bpo-42130.Ho8dUh.rst b/Misc/NEWS.d/next/Library/2021-11-29-17-17-45.bpo-42130.Ho8dUh.rst new file mode 100644 index 00000000000000..8e6e00fadf0102 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2021-11-29-17-17-45.bpo-42130.Ho8dUh.rst @@ -0,0 +1 @@ +Fix issue where ``asyncio.wait_for()`` may suppress an explicit cancellation. From a8f5c1fe4d7c86c91f72b612072153d6e388253d Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 1 Dec 2021 17:05:50 +0000 Subject: [PATCH 11/19] Compare to tuple Co-authored-by: Andrew Svetlov --- Lib/asyncio/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 66962c2162f796..a3c75fa918ba22 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -433,7 +433,7 @@ async def wait_for(fut, timeout): try: await waiter except exceptions.CancelledError as e: - if fut.done() and _SENTINEL in e.args: + if fut.done() and e.args == (_SENTINEL,): return fut.result() else: fut.remove_done_callback(cb) From 5de6b796959cdd4a74107a28dbd50589088ba8ef Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 1 Dec 2021 17:12:21 +0000 Subject: [PATCH 12/19] Use enum for sentinel --- Lib/asyncio/tasks.py | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index a3c75fa918ba22..0afb6b8bcf58d9 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -25,7 +25,8 @@ from . import futures from .coroutines import _is_coroutine -_SENTINEL = object() +_SENTINEL = enum.Enum("_SENTINEL", "sentinel") +sentinel = _SENTINEL.sentinel # Helper to generate new task names # This uses itertools.count() instead of a "+= 1" operation because the latter @@ -433,7 +434,7 @@ async def wait_for(fut, timeout): try: await waiter except exceptions.CancelledError as e: - if fut.done() and e.args == (_SENTINEL,): + if fut.done() and e.args == (sentinel,): return fut.result() else: fut.remove_done_callback(cb) @@ -514,7 +515,7 @@ async def _cancel_and_wait(fut, loop): fut.add_done_callback(cb) try: - fut.cancel(_SENTINEL) + fut.cancel(sentinel) # We cannot wait on *fut* directly to make # sure _cancel_and_wait itself is reliably cancellable. await waiter From 5a79cf2d3b9dfdc5f439986fa11d9c1cc49424e1 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 1 Dec 2021 17:26:42 +0000 Subject: [PATCH 13/19] Missing import --- Lib/asyncio/tasks.py | 1 + 1 file changed, 1 insertion(+) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 0afb6b8bcf58d9..118b0c50b5ef8a 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -11,6 +11,7 @@ import concurrent.futures import contextvars +import enum import functools import inspect import itertools From 11e12141c20e8efb2387a5e19145359b2d6c748a Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Mon, 6 Dec 2021 23:35:59 +0000 Subject: [PATCH 14/19] Update tasks.py --- Lib/asyncio/tasks.py | 21 +++++++-------------- 1 file changed, 7 insertions(+), 14 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 118b0c50b5ef8a..96479b7b548f35 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -11,7 +11,6 @@ import concurrent.futures import contextvars -import enum import functools import inspect import itertools @@ -26,9 +25,6 @@ from . import futures from .coroutines import _is_coroutine -_SENTINEL = enum.Enum("_SENTINEL", "sentinel") -sentinel = _SENTINEL.sentinel - # Helper to generate new task names # This uses itertools.count() instead of a "+= 1" operation because the latter # is not thread safe. See bpo-11866 for a longer explanation. @@ -435,15 +431,12 @@ async def wait_for(fut, timeout): try: await waiter except exceptions.CancelledError as e: - if fut.done() and e.args == (sentinel,): - return fut.result() - else: - fut.remove_done_callback(cb) - # We must ensure that the task is not running - # after wait_for() returns. - # See https://bugs.python.org/issue32751 - await _cancel_and_wait(fut, loop=loop) - raise + fut.remove_done_callback(cb) + # We must ensure that the task is not running + # after wait_for() returns. + # See https://bugs.python.org/issue32751 + await _cancel_and_wait(fut, loop=loop) + raise if fut.done(): return fut.result() @@ -516,7 +509,7 @@ async def _cancel_and_wait(fut, loop): fut.add_done_callback(cb) try: - fut.cancel(sentinel) + fut.cancel() # We cannot wait on *fut* directly to make # sure _cancel_and_wait itself is reliably cancellable. await waiter From 3a351268f41ec8ae9d8630c2470e9030cbc92431 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Mon, 6 Dec 2021 23:36:42 +0000 Subject: [PATCH 15/19] Update tasks.py --- Lib/asyncio/tasks.py | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 96479b7b548f35..7fa66a0fd74b64 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -430,7 +430,7 @@ async def wait_for(fut, timeout): # wait until the future completes or the timeout try: await waiter - except exceptions.CancelledError as e: + except exceptions.CancelledError: fut.remove_done_callback(cb) # We must ensure that the task is not running # after wait_for() returns. From df15ac1789f2c3eaf357477beda70eca8c2c3431 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 15 Dec 2021 15:23:55 +0000 Subject: [PATCH 16/19] Update test_tasks.py --- Lib/test/test_asyncio/test_tasks.py | 69 ++++++++++++++++++++--------- 1 file changed, 49 insertions(+), 20 deletions(-) diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index a2673d64700b34..5879dd128d1016 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1009,34 +1009,63 @@ def gen(): self.assertEqual(res, "ok") def test_wait_for_cancellation_race_condition(self): + def gen(): + yield 0.1 + yield 0.1 + yield 0.1 + yield 0.1 + + loop = self.new_test_loop(gen) + + fut = self.new_future(loop) + # Test that if task is cancelled at the same time the future + # completes, the result is still returned. + loop.call_later(0.1, fut.set_result, "ok") + task = loop.create_task(asyncio.wait_for(fut, timeout=1)) + loop.call_later(0.1, task.cancel) + res = loop.run_until_complete(task) + self.assertEqual(res, "ok") + + def test_wait_for_suppress_cancellation(self): + def gen(): + yield + yield 0.1 + yield 0.1 + yield 0.1 + async def inner(): - with contextlib.suppress(asyncio.CancelledError): + try: await asyncio.sleep(1) - return 1 + except asyncio.CancelledError: + return "ok" - async def main(): - result = await asyncio.wait_for(inner(), timeout=.01) - assert result == 1 + loop = self.new_test_loop(gen) - asyncio.run(main()) + fut = self.new_future(loop) + task = loop.create_task(asyncio.wait_for(inner(), timeout=1)) + loop.call_later(0.1, task.cancel) + # Cancellation is suppressed in inner(), so should still return here. + res = loop.run_until_complete(task) + self.assertEqual(res, "ok") - def test_wait_for_does_not_suppress_cancellation(self): - async def inner(): - return + def test_wait_for_cancellation_inner_race_condition(self): + def gen(): + yield + yield 0.1 + yield 0 + yield 0 - async def with_for_coro(): - await asyncio.wait_for(inner(), timeout=1) - assert False, 'End of with_for_coro. Should not be reached!' + async def inner(): + # Test that if fut completes at same time as timeout, the result + # is still returned. + return await asyncio.wait_for(fut, timeout=1) - async def main(): - task = asyncio.create_task(with_for_coro()) - await asyncio.sleep(0) - self.assertFalse(task.done()) - task.cancel() - with self.assertRaises(asyncio.CancelledError): - await task + loop = self.new_test_loop(gen) - asyncio.run(main()) + fut = self.new_future(loop) + loop.call_later(0.1, fut.set_result, "ok") + res = loop.run_until_complete(asyncio.wait_for(inner(), timeout=0.1)) + self.assertEqual(res, "ok") def test_wait_for_waits_for_task_cancellation(self): loop = asyncio.new_event_loop() From 73743665181b9e93b0e0611651a568d1ff554963 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 15 Dec 2021 15:25:47 +0000 Subject: [PATCH 17/19] Update tasks.py --- Lib/asyncio/tasks.py | 29 ++++++++++++++++++++--------- 1 file changed, 20 insertions(+), 9 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 7fa66a0fd74b64..e13c0915ca7737 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -389,7 +389,7 @@ def _release_waiter(waiter, *args): waiter.set_result(None) -async def wait_for(fut, timeout): +async def wait_for(fut, timeout, *, loop=None): """Wait for the single Future or coroutine to complete, with timeout. Coroutine will be wrapped in Task. @@ -402,7 +402,12 @@ async def wait_for(fut, timeout): This function is a coroutine. """ - loop = events.get_running_loop() + if loop is None: + loop = events.get_running_loop() + else: + warnings.warn("The loop argument is deprecated since Python 3.8, " + "and scheduled for removal in Python 3.10.", + DeprecationWarning, stacklevel=2) if timeout is None: return await fut @@ -421,7 +426,10 @@ async def wait_for(fut, timeout): waiter = loop.create_future() timeout_handle = loop.call_later(timeout, _release_waiter, waiter) - cb = functools.partial(_release_waiter, waiter) + #cb = functools.partial(_release_waiter, waiter) + + def cb(f): + _release_waiter(waiter) fut = ensure_future(fut, loop=loop) fut.add_done_callback(cb) @@ -431,12 +439,15 @@ async def wait_for(fut, timeout): try: await waiter except exceptions.CancelledError: - fut.remove_done_callback(cb) - # We must ensure that the task is not running - # after wait_for() returns. - # See https://bugs.python.org/issue32751 - await _cancel_and_wait(fut, loop=loop) - raise + if fut.done(): + return fut.result() + else: + fut.remove_done_callback(cb) + # We must ensure that the task is not running + # after wait_for() returns. + # See https://bugs.python.org/issue32751 + await _cancel_and_wait(fut, loop=loop) + return fut.result() if fut.done(): return fut.result() From c6941733a7200d0a3bc4f18b8bf4567f18a0795a Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 15 Dec 2021 15:27:22 +0000 Subject: [PATCH 18/19] Revert mistaken changes. --- Lib/asyncio/tasks.py | 14 +++----------- 1 file changed, 3 insertions(+), 11 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index e13c0915ca7737..a4d8b3f24fe287 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -389,7 +389,7 @@ def _release_waiter(waiter, *args): waiter.set_result(None) -async def wait_for(fut, timeout, *, loop=None): +async def wait_for(fut, timeout): """Wait for the single Future or coroutine to complete, with timeout. Coroutine will be wrapped in Task. @@ -402,12 +402,7 @@ async def wait_for(fut, timeout, *, loop=None): This function is a coroutine. """ - if loop is None: - loop = events.get_running_loop() - else: - warnings.warn("The loop argument is deprecated since Python 3.8, " - "and scheduled for removal in Python 3.10.", - DeprecationWarning, stacklevel=2) + loop = events.get_running_loop() if timeout is None: return await fut @@ -426,10 +421,7 @@ async def wait_for(fut, timeout, *, loop=None): waiter = loop.create_future() timeout_handle = loop.call_later(timeout, _release_waiter, waiter) - #cb = functools.partial(_release_waiter, waiter) - - def cb(f): - _release_waiter(waiter) + cb = functools.partial(_release_waiter, waiter) fut = ensure_future(fut, loop=loop) fut.add_done_callback(cb) From cb593779071a0baf92c550df8082c7dfc48b7467 Mon Sep 17 00:00:00 2001 From: Sam Bull Date: Wed, 15 Dec 2021 15:31:29 +0000 Subject: [PATCH 19/19] Update 2021-11-29-17-17-45.bpo-42130.Ho8dUh.rst --- .../next/Library/2021-11-29-17-17-45.bpo-42130.Ho8dUh.rst | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Misc/NEWS.d/next/Library/2021-11-29-17-17-45.bpo-42130.Ho8dUh.rst b/Misc/NEWS.d/next/Library/2021-11-29-17-17-45.bpo-42130.Ho8dUh.rst index 8e6e00fadf0102..ee837d5636cfcf 100644 --- a/Misc/NEWS.d/next/Library/2021-11-29-17-17-45.bpo-42130.Ho8dUh.rst +++ b/Misc/NEWS.d/next/Library/2021-11-29-17-17-45.bpo-42130.Ho8dUh.rst @@ -1 +1,2 @@ -Fix issue where ``asyncio.wait_for()`` may suppress an explicit cancellation. +Fix issue where ``asyncio.wait_for()`` is cancelled when the coro +actually suppresses the cancellation.