diff -r a14b93a4eb49 Lib/asyncio/tasks.py --- a/Lib/asyncio/tasks.py Mon Jun 20 05:29:54 2016 +0300 +++ b/Lib/asyncio/tasks.py Mon Jun 20 12:48:50 2016 +0200 @@ -579,9 +579,10 @@ def cancel(self): if self.done(): return False + ret = False for child in self._children: - child.cancel() - return True + ret |= child.cancel() + return ret def gather(*coros_or_futures, loop=None, return_exceptions=False): diff -r a14b93a4eb49 Lib/test/test_asyncio/test_tasks.py --- a/Lib/test/test_asyncio/test_tasks.py Mon Jun 20 05:29:54 2016 +0300 +++ b/Lib/test/test_asyncio/test_tasks.py Mon Jun 20 12:48:50 2016 +0200 @@ -1857,6 +1857,36 @@ 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: