Skip to content

Commit 4e0cffb

Browse files
[3.14] gh-155143: Fix asyncio.shield() leaking tasks via await-graph (GH-155144) (#157319)
gh-155143: Fix asyncio.shield() leaking tasks via await-graph (GH-155144) (cherry picked from commit fbd2e01) Co-authored-by: Andrew Geng <pteromys@gmail.com>
1 parent 9dc22a1 commit 4e0cffb

3 files changed

Lines changed: 22 additions & 0 deletions

File tree

Lib/asyncio/tasks.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -999,6 +999,9 @@ def _outer_done_callback(outer):
999999
# Keep only one callback to log on cancel
10001000
inner.remove_done_callback(_log_on_exception)
10011001
inner.add_done_callback(_log_on_exception)
1002+
if cur_task is not None:
1003+
inner.remove_done_callback(_clear_awaited_by_callback)
1004+
futures.future_discard_from_awaited_by(inner, cur_task)
10021005

10031006
if cur_task is not None:
10041007
inner.add_done_callback(_clear_awaited_by_callback)

Lib/test/test_asyncio/test_tasks.py

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2128,6 +2128,8 @@ def test_shield_cancel_outer(self):
21282128
test_utils.run_briefly(self.loop)
21292129
self.assertTrue(outer.cancelled())
21302130
self.assertEqual(0, 0 if outer._callbacks is None else len(outer._callbacks))
2131+
self.assertFalse(inner._asyncio_awaited_by)
2132+
self.assertTrue({f for f, _ctx in inner._callbacks or []} <= {asyncio.tasks._log_on_exception})
21312133

21322134
def test_shield_cancel_outer_result(self):
21332135
mock_handler = mock.Mock()
@@ -2153,6 +2155,21 @@ def test_shield_cancel_outer_exception(self):
21532155
test_utils.run_briefly(self.loop)
21542156
mock_handler.assert_called_once()
21552157

2158+
def test_shield_cancel_outer_in_task(self):
2159+
inner = self.new_future(self.loop)
2160+
2161+
async def coro():
2162+
outer = asyncio.shield(inner)
2163+
self.assertNotEqual(0, len(inner._callbacks))
2164+
outer.cancel()
2165+
await asyncio.sleep(0)
2166+
self.assertTrue(outer.cancelled())
2167+
2168+
task = self.new_task(self.loop, coro())
2169+
self.loop.run_until_complete(task)
2170+
self.assertFalse(inner._asyncio_awaited_by)
2171+
self.assertTrue({f for f, _ctx in inner._callbacks or []} <= {asyncio.tasks._log_on_exception})
2172+
21562173
def test_shield_duplicate_log_once(self):
21572174
mock_handler = mock.Mock()
21582175
self.loop.set_exception_handler(mock_handler)
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
Fix :func:`asyncio.shield` leaking the calling task via the await-graph and
2+
callbacks when called on a future that never resolves.

0 commit comments

Comments
 (0)