summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authortwisteroid ambassador <twisteroidambassador@users.noreply.github.com>2018-10-09 15:30:21 (GMT)
committerYury Selivanov <yury@magic.io>2018-10-09 15:30:21 (GMT)
commitc880ffe7d2ce2fedb1831918c8a36e3623e0fb76 (patch)
treefeb0b9505af0d13fd5284514383e0a68a7dbc730
parent79d21331e605fdc941f947621846b8563485aab6 (diff)
downloadcpython-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.py5
-rw-r--r--Lib/test/test_asyncio/test_base_events.py68
-rw-r--r--Misc/NEWS.d/next/Library/2018-10-09-11-01-16.bpo-34769.cSkkZt.rst2
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.