From 1f9e069773072ec22fe27ee3f572807fec7363a2 Mon Sep 17 00:00:00 2001 From: Romain Picard Date: Mon, 5 Nov 2018 17:51:23 +0100 Subject: [PATCH 1/2] bpo-35125: remove inner callback on outer cancellation in asyncio shield When the future returned by shield is cancelled, its completion callback of the inner future is not removed. This makes the callback list of inner inner future grow each time a shield is created and cancelled. This change unregistrer the callback from the inner future when the outer future is cancelled. --- Lib/asyncio/tasks.py | 10 ++++++++-- Lib/test/test_asyncio/test_tasks.py | 11 ++++++++++- 2 files changed, 18 insertions(+), 3 deletions(-) diff --git a/Lib/asyncio/tasks.py b/Lib/asyncio/tasks.py index 15422da1b3b2e0..68eafc96bf7473 100644 --- a/Lib/asyncio/tasks.py +++ b/Lib/asyncio/tasks.py @@ -816,7 +816,7 @@ def shield(arg, *, loop=None): loop = futures._get_loop(inner) outer = loop.create_future() - def _done_callback(inner): + def _inner_done_callback(inner): if outer.cancelled(): if not inner.cancelled(): # Mark inner's result as retrieved. @@ -832,7 +832,13 @@ def _done_callback(inner): else: outer.set_result(inner.result()) - inner.add_done_callback(_done_callback) + + def _outer_done_callback(outer): + if not inner.done(): + inner.remove_done_callback(_inner_done_callback) + + inner.add_done_callback(_inner_done_callback) + outer.add_done_callback(_outer_done_callback) return outer diff --git a/Lib/test/test_asyncio/test_tasks.py b/Lib/test/test_asyncio/test_tasks.py index c65d1f2440d2e6..458ef3ff65d187 100644 --- a/Lib/test/test_asyncio/test_tasks.py +++ b/Lib/test/test_asyncio/test_tasks.py @@ -1768,7 +1768,7 @@ def test_shield_exception(self): test_utils.run_briefly(self.loop) self.assertIs(outer.exception(), exc) - def test_shield_cancel(self): + def test_shield_cancel_inner(self): inner = self.new_future(self.loop) outer = asyncio.shield(inner) test_utils.run_briefly(self.loop) @@ -1776,6 +1776,15 @@ def test_shield_cancel(self): test_utils.run_briefly(self.loop) self.assertTrue(outer.cancelled()) + def test_shield_cancel_outer(self): + inner = self.new_future(self.loop) + outer = asyncio.shield(inner) + test_utils.run_briefly(self.loop) + outer.cancel() + test_utils.run_briefly(self.loop) + self.assertTrue(outer.cancelled()) + self.assertEqual(0, 0 if outer._callbacks is None else len(outer._callbacks)) + def test_shield_shortcut(self): fut = self.new_future(self.loop) fut.set_result(42) From 915a3224d6ca8709814ce6c3ebeaeb6bca608b98 Mon Sep 17 00:00:00 2001 From: Romain Picard Date: Fri, 15 Feb 2019 17:19:46 +0100 Subject: [PATCH 2/2] bpo-35125: added NEWS entry --- .../NEWS.d/next/Library/2019-02-15-17-18-50.bpo-35125.h0xk0f.rst | 1 + 1 file changed, 1 insertion(+) create mode 100644 Misc/NEWS.d/next/Library/2019-02-15-17-18-50.bpo-35125.h0xk0f.rst diff --git a/Misc/NEWS.d/next/Library/2019-02-15-17-18-50.bpo-35125.h0xk0f.rst b/Misc/NEWS.d/next/Library/2019-02-15-17-18-50.bpo-35125.h0xk0f.rst new file mode 100644 index 00000000000000..2e28a25d2415a2 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2019-02-15-17-18-50.bpo-35125.h0xk0f.rst @@ -0,0 +1 @@ +Asyncio: Remove inner callback on outer cancellation in shield