From 3d67615a485f4769eec5927e17989b31d6917e1c Mon Sep 17 00:00:00 2001 From: Yury Selivanov Date: Fri, 21 Oct 2016 17:22:17 -0400 Subject: Issue #26923: Fix asyncio.Gather to refuse being cancelled once all children are done. Patch by Johannes Ebke. --- Lib/asyncio/tasks.py | 6 ++++-- Lib/test/test_asyncio/test_tasks.py | 30 ++++++++++++++++++++++++++++++ Misc/NEWS | 4 ++++ 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 ---- -- cgit v0.12 5a6ac51c9d5eddf58'>root/Lib/test/test_unpack.py
blob: b1c483d4d550ad47c54eeb93931a7a83ca044d62 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
doctests = """

Unpack tuple

    >>> t = (1, 2, 3)
    >>> a, b, c = t
    >>> a == 1 and b == 2 and c == 3
    True

Unpack list

    >>> l = [4, 5, 6]
    >>> a, b, c = l
    >>> a == 4 and b == 5 and c == 6
    True

Unpack implied tuple

    >>> a, b, c = 7, 8, 9
    >>> a == 7 and b == 8 and c == 9
    True

Unpack string... fun!

    >>> a, b, c = 'one'
    >>> a == 'o' and b == 'n' and c == 'e'
    True

Unpack generic sequence

    >>> class Seq:
    ...     def __getitem__(self, i):
    ...         if i >= 0 and i < 3: return i
    ...         raise IndexError
    ...
    >>> a, b, c = Seq()
    >>> a == 0 and b == 1 and c == 2
    True

Single element unpacking, with extra syntax

    >>> st = (99,)
    >>> sl = [100]
    >>> a, = st
    >>> a
    99
    >>> b, = sl
    >>> b
    100

Now for some failures

Unpacking non-sequence

    >>> a, b, c = 7
    Traceback (most recent call last):
      ...
    TypeError: 'int' object is not iterable

Unpacking tuple of wrong size

    >>> a, b = t
    Traceback (most recent call last):
      ...
    ValueError: too many values to unpack (expected 2)

Unpacking tuple of wrong size

    >>> a, b = l
    Traceback (most recent call last):
      ...
    ValueError: too many values to unpack (expected 2)

Unpacking sequence too short

    >>> a, b, c, d = Seq()
    Traceback (most recent call last):
      ...
    ValueError: need more than 3 values to unpack

Unpacking sequence too long

    >>> a, b = Seq()
    Traceback (most recent call last):
      ...
    ValueError: too many values to unpack (expected 2)

Unpacking a sequence where the test for too long raises a different kind of
error

    >>> class BozoError(Exception):
    ...     pass
    ...
    >>> class BadSeq:
    ...     def __getitem__(self, i):
    ...         if i >= 0 and i < 3:
    ...             return i
    ...         elif i == 3:
    ...             raise BozoError
    ...         else:
    ...             raise IndexError
    ...

Trigger code while not expecting an IndexError (unpack sequence too long, wrong
error)

    >>> a, b, c, d, e = BadSeq()
    Traceback (most recent call last):
      ...
    test.test_unpack.BozoError

Trigger code while expecting an IndexError (unpack sequence too short, wrong
error)

    >>> a, b, c = BadSeq()
    Traceback (most recent call last):
      ...
    test.test_unpack.BozoError

"""

__test__ = {'doctests' : doctests}

def test_main(verbose=False):
    from test import support
    from test import test_unpack
    support.run_doctest(test_unpack, verbose)

if __name__ == "__main__":
    test_main(verbose=True)