Skip to content

bpo-34769: Thread safety for _asyncgen_finalizer_hook(). #9716

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
Merged
Show file tree
Hide file tree
Changes from 7 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 1 addition & 4 deletions Lib/asyncio/base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -477,10 +477,7 @@ def _check_closed(self):
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:
Expand Down
70 changes: 70 additions & 0 deletions Lib/test/test_asyncio/test_base_events.py
Original file line number Diff line number Diff line change
Expand Up @@ -926,6 +926,76 @@ def test_run_forever_pre_stopped(self):
self.loop.run_forever()
self.loop._selector.select.assert_called_once_with(0)

async def leave_unfinalized_asyncgen(self):
# The following should create an async generator, iterate
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Let's slim down the comments:

# 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

# it partially, and leave it to be garbage collected.
# This depends on implementation details of the garbage
# collector, and may stop working in future versions of
# cpython or in other implementations. In that case,
# the tests below on async generator finalization may
# break as well.
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
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
Fix for async generators not finalizing when event loop in debug mode and
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

...event loop is in...

garbage collector runs in another thread.