bpo-32684: Fix gather to propagate cancel of itself with return_exceptions (GH-7209) (#7222)

(cherry picked from commit 863b674909)

Co-authored-by: Yury Selivanov <yury@magic.io>
This commit is contained in:
Miss Islington (bot) 2018-05-29 15:29:12 -07:00 committed by Yury Selivanov
parent 2a7eb0b531
commit 036434273e
4 changed files with 46 additions and 2 deletions

View File

@ -640,6 +640,10 @@ Task functions
outer Future is *not* cancelled in this case. (This is to prevent the
cancellation of one child to cause other children to be cancelled.)
.. versionchanged:: 3.7.0
If the *gather* itself is cancelled, the cancellation is propagated
regardless of *return_exceptions*.
.. function:: iscoroutine(obj)
Return ``True`` if *obj* is a :ref:`coroutine object <coroutine>`,

View File

@ -591,6 +591,7 @@ class _GatheringFuture(futures.Future):
def __init__(self, children, *, loop=None):
super().__init__(loop=loop)
self._children = children
self._cancel_requested = False
def cancel(self):
if self.done():
@ -599,6 +600,11 @@ class _GatheringFuture(futures.Future):
for child in self._children:
if child.cancel():
ret = True
if ret:
# If any child tasks were actually cancelled, we should
# propagate the cancellation request regardless of
# *return_exceptions* argument. See issue 32684.
self._cancel_requested = True
return ret
@ -673,7 +679,13 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False):
res = fut.result()
results.append(res)
outer.set_result(results)
if outer._cancel_requested:
# If gather is being cancelled we must propagate the
# cancellation regardless of *return_exceptions* argument.
# See issue 32684.
outer.set_exception(futures.CancelledError())
else:
outer.set_result(results)
arg_to_fut = {}
children = []

View File

@ -2037,7 +2037,7 @@ class BaseTaskTests:
def test_cancel_wait_for(self):
self._test_cancel_wait_for(60.0)
def test_cancel_gather(self):
def test_cancel_gather_1(self):
"""Ensure that a gathering future refuses to be cancelled once all
children are done"""
loop = asyncio.new_event_loop()
@ -2067,6 +2067,33 @@ class BaseTaskTests:
self.assertFalse(gather_task.cancelled())
self.assertEqual(gather_task.result(), [42])
def test_cancel_gather_2(self):
loop = asyncio.new_event_loop()
self.addCleanup(loop.close)
async def test():
time = 0
while True:
time += 0.05
await asyncio.gather(asyncio.sleep(0.05),
return_exceptions=True,
loop=loop)
if time > 1:
return
async def main():
qwe = asyncio.Task(test())
await asyncio.sleep(0.2)
qwe.cancel()
try:
await qwe
except asyncio.CancelledError:
pass
else:
self.fail('gather did not propagate the cancellation request')
loop.run_until_complete(main())
def test_exception_traceback(self):
# See http://bugs.python.org/issue28843

View File

@ -0,0 +1 @@
Fix gather to propagate cancellation of itself even with return_exceptions.