From 3d67615a485f4769eec5927e17989b31d6917e1c Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Fri, 21 Oct 2016 17:22:17 -0400 Subject: [PATCH] Issue #26923: Fix asyncio.Gather to refuse being cancelled once all children are done. Patch by Johannes Ebke. --- Lib/asyncio/tasks.py | 6 ++++-- Lib/test/test_asyncio/test_tasks.py | 30 +++++++++++++++++++++++++++++ Misc/NEWS | 4 ++++ 3 files changed, 38 insertions(+), 2 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 14949d13407..8852aa5ad2a 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -592,9 +592,11 @@ class _GatheringFuture(futures.Future): def cancel(self): if self.done(): return False + ret = False for child in self._children: - child.cancel() - return True + if child.cancel(): + ret = True + return ret def gather(*coros_or_futures, loop=None, return_exceptions=False): diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index a5af7d18cf3..1ceb9b28cb3 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1899,6 +1899,36 @@ class TaskTests(test_utils.TestCase): def test_cancel_wait_for(self): self._test_cancel_wait_for(60.0) + def test_cancel_gather(self): + """Ensure that a gathering future refuses to be cancelled once all + children are done""" + loop = asyncio.new_event_loop() + self.addCleanup(loop.close) + + fut = asyncio.Future(loop=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) + gather_future = asyncio.gather(child_coro(), loop=loop) + gather_task = asyncio.ensure_future(gather_future, loop=loop) + + cancel_result = None + def cancelling_callback(_): + nonlocal cancel_result + cancel_result = gather_task.cancel() + fut.add_done_callback(cancelling_callback) + + fut.set_result(42) # calls the cancelling_callback after fut is done() + + # At this point the task should complete. + loop.run_until_complete(gather_task) + + # Python issue #26923: asyncio.gather drops cancellation + self.assertEqual(cancel_result, False) + self.assertFalse(gather_task.cancelled()) + self.assertEqual(gather_task.result(), [42]) + class GatherTestsBase: diff --git a/Misc/NEWS b/Misc/NEWS index 2d50cf6ad53..48bd626c1d7 100644 --- a/Misc/NEWS +++ b/Misc/NEWS @@ -398,6 +398,10 @@ Library - Issue #27972: Prohibit Tasks to await on themselves. +- Issue #26923: Fix asyncio.Gather to refuse being cancelled once all + children are done. + Patch by Johannes Ebke. + IDLE ----