summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--Doc/library/asyncio-task.rst4
-rw-r--r--Lib/asyncio/tasks.py11
-rw-r--r--Lib/test/test_asyncio/test_tasks.py29
-rw-r--r--Misc/NEWS.d/next/Library/2018-05-29-12-51-18.bpo-32684.ZEIism.rst1
4 files changed, 43 insertions, 2 deletions
diff --git a/Doc/library/asyncio-task.rst b/Doc/library/asyncio-task.rst
index 5b801aa..c038333 100644
--- a/Doc/library/asyncio-task.rst
+++ b/Doc/library/asyncio-task.rst
@@ -568,6 +568,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.6.6
+ 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>`,
diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py
index a294dfb..4cd2c6a 100644
--- a/Lib/asyncio/tasks.py
+++ b/Lib/asyncio/tasks.py
@@ -548,6 +548,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():
@@ -556,6 +557,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
@@ -636,7 +642,10 @@ def gather(*coros_or_futures, loop=None, return_exceptions=False):
results[i] = res
nfinished += 1
if nfinished == nchildren:
- outer.set_result(results)
+ if outer._cancel_requested:
+ outer.set_exception(futures.CancelledError())
+ else:
+ outer.set_result(results)
for i, fut in enumerate(children):
fut.add_done_callback(functools.partial(_done_callback, i))
diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py
index f41160b..7c51873 100644
--- a/Lib/test/test_asyncio/test_tasks.py
+++ b/Lib/test/test_asyncio/test_tasks.py
@@ -1991,7 +1991,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()
@@ -2021,6 +2021,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, loop=loop),
+ return_exceptions=True,
+ loop=loop)
+ if time > 1:
+ return
+
+ async def main():
+ qwe = self.new_task(loop, test())
+ await asyncio.sleep(0.2, loop=loop)
+ 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
diff --git a/Misc/NEWS.d/next/Library/2018-05-29-12-51-18.bpo-32684.ZEIism.rst b/Misc/NEWS.d/next/Library/2018-05-29-12-51-18.bpo-32684.ZEIism.rst
new file mode 100644
index 0000000..b360bbc
--- /dev/null
+++ b/Misc/NEWS.d/next/Library/2018-05-29-12-51-18.bpo-32684.ZEIism.rst
@@ -0,0 +1 @@
+Fix gather to propagate cancellation of itself even with return_exceptions.