summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorYury Selivanov <yury@magic.io>2016-10-21 21:22:17 (GMT)
committerYury Selivanov <yury@magic.io>2016-10-21 21:22:17 (GMT)
commit3d67615a485f4769eec5927e17989b31d6917e1c (patch)
tree9e93ca98520dd3c407c6e87bff613707e5b46d78
parentf8c1505736cb6eeb264e58c28d1f94d8fe7cad34 (diff)
downloadcpython-3d67615a485f4769eec5927e17989b31d6917e1c.zip
cpython-3d67615a485f4769eec5927e17989b31d6917e1c.tar.gz
cpython-3d67615a485f4769eec5927e17989b31d6917e1c.tar.bz2
Issue #26923: Fix asyncio.Gather to refuse being cancelled once all children are done.
Patch by Johannes Ebke.
-rw-r--r--Lib/asyncio/tasks.py6
-rw-r--r--Lib/test/test_asyncio/test_tasks.py30
-rw-r--r--Misc/NEWS4
3 files changed, 38 insertions, 2 deletions
diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py
index 14949d1..8852aa5 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 a5af7d1..1ceb9b2 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 2d50cf6..48bd626 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
----