diff options
author | twisteroid ambassador <twisteroidambassador@users.noreply.github.com> | 2018-10-09 15:30:21 (GMT) |
---|---|---|
committer | Yury Selivanov <yury@magic.io> | 2018-10-09 15:30:21 (GMT) |
commit | c880ffe7d2ce2fedb1831918c8a36e3623e0fb76 (patch) | |
tree | feb0b9505af0d13fd5284514383e0a68a7dbc730 | |
parent | 79d21331e605fdc941f947621846b8563485aab6 (diff) | |
download | cpython-c880ffe7d2ce2fedb1831918c8a36e3623e0fb76.zip cpython-c880ffe7d2ce2fedb1831918c8a36e3623e0fb76.tar.gz cpython-c880ffe7d2ce2fedb1831918c8a36e3623e0fb76.tar.bz2 |
bpo-34769: Thread safety for _asyncgen_finalizer_hook(). (GH-9716)
-rw-r--r-- | Lib/asyncio/base_events.py | 5 | ||||
-rw-r--r-- | Lib/test/test_asyncio/test_base_events.py | 68 | ||||
-rw-r--r-- | Misc/NEWS.d/next/Library/2018-10-09-11-01-16.bpo-34769.cSkkZt.rst | 2 |
3 files changed, 71 insertions, 4 deletions
diff --git a/Lib/asyncio/base_events.py b/Lib/asyncio/base_events.py index 780a061..3726c55 100644 --- a/Lib/asyncio/base_events.py +++ b/Lib/asyncio/base_events.py @@ -477,10 +477,7 @@ class BaseEventLoop(events.AbstractEventLoop): def _asyncgen_finalizer_hook(self, agen): self._asyncgens.discard(agen) if not self.is_closed(): - self.create_task(agen.aclose()) - # Wake up the loop if the finalizer was called from - # a different thread. - self._write_to_self() + self.call_soon_threadsafe(self.create_task, agen.aclose()) def _asyncgen_firstiter_hook(self, agen): if self._asyncgens_shutdown_called: diff --git a/Lib/test/test_asyncio/test_base_events.py b/Lib/test/test_asyncio/test_base_events.py index d15a9c6..6d544d1 100644 --- a/Lib/test/test_asyncio/test_base_events.py +++ b/Lib/test/test_asyncio/test_base_events.py @@ -926,6 +926,74 @@ class BaseEventLoopTests(test_utils.TestCase): self.loop.run_forever() self.loop._selector.select.assert_called_once_with(0) + async def leave_unfinalized_asyncgen(self): + # Create an async generator, iterate it partially, and leave it + # to be garbage collected. + # Used in async generator finalization tests. + # Depends on implementation details of garbage collector. Changes + # in gc may break this function. + status = {'started': False, + 'stopped': False, + 'finalized': False} + + async def agen(): + status['started'] = True + try: + for item in ['ZERO', 'ONE', 'TWO', 'THREE', 'FOUR']: + yield item + finally: + status['finalized'] = True + + ag = agen() + ai = ag.__aiter__() + + async def iter_one(): + try: + item = await ai.__anext__() + except StopAsyncIteration: + return + if item == 'THREE': + status['stopped'] = True + return + asyncio.create_task(iter_one()) + + asyncio.create_task(iter_one()) + return status + + def test_asyncgen_finalization_by_gc(self): + # Async generators should be finalized when garbage collected. + self.loop._process_events = mock.Mock() + self.loop._write_to_self = mock.Mock() + with support.disable_gc(): + status = self.loop.run_until_complete(self.leave_unfinalized_asyncgen()) + while not status['stopped']: + test_utils.run_briefly(self.loop) + self.assertTrue(status['started']) + self.assertTrue(status['stopped']) + self.assertFalse(status['finalized']) + support.gc_collect() + test_utils.run_briefly(self.loop) + self.assertTrue(status['finalized']) + + def test_asyncgen_finalization_by_gc_in_other_thread(self): + # Python issue 34769: If garbage collector runs in another + # thread, async generators will not finalize in debug + # mode. + self.loop._process_events = mock.Mock() + self.loop._write_to_self = mock.Mock() + self.loop.set_debug(True) + with support.disable_gc(): + status = self.loop.run_until_complete(self.leave_unfinalized_asyncgen()) + while not status['stopped']: + test_utils.run_briefly(self.loop) + self.assertTrue(status['started']) + self.assertTrue(status['stopped']) + self.assertFalse(status['finalized']) + self.loop.run_until_complete( + self.loop.run_in_executor(None, support.gc_collect)) + test_utils.run_briefly(self.loop) + self.assertTrue(status['finalized']) + class MyProto(asyncio.Protocol): done = None diff --git a/Misc/NEWS.d/next/Library/2018-10-09-11-01-16.bpo-34769.cSkkZt.rst b/Misc/NEWS.d/next/Library/2018-10-09-11-01-16.bpo-34769.cSkkZt.rst new file mode 100644 index 0000000..fc034c9 --- /dev/null +++ b/Misc/NEWS.d/next/Library/2018-10-09-11-01-16.bpo-34769.cSkkZt.rst @@ -0,0 +1,2 @@ +Fix for async generators not finalizing when event loop is in debug mode and +garbage collector runs in another thread. |