]> granicus.if.org Git - python/commitdiff
Issue #26923: Fix asyncio.Gather to refuse being cancelled once all children are...
authorYury Selivanov <yury@magic.io>
Fri, 21 Oct 2016 21:22:17 +0000 (17:22 -0400)
committerYury Selivanov <yury@magic.io>
Fri, 21 Oct 2016 21:22:17 +0000 (17:22 -0400)
Patch by Johannes Ebke.

Lib/asyncio/tasks.py
Lib/test/test_asyncio/test_tasks.py
Misc/NEWS

index 14949d1340719c1149da3c07d412dabdaa270d79..8852aa5ad2a2eb1e3a2760585048f626005afca4 100644 (file)
@@ -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):
index a5af7d18cf34e21a484257ee53dd44f1afbb35c6..1ceb9b28cb38ef901d2f7fd2de866eab130d4bba 100644 (file)
@@ -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:
 
index 2d50cf6ad53376a504ce3ea7e1d0bb134d794010..48bd626c1d7850de072988d8bca13a140c590303 100644 (file)
--- 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
 ----