Skip to content

Commit cca57d9

Browse files
committed
Cleanups after code review
1 parent 0a49fd9 commit cca57d9

File tree

3 files changed

+34
-14
lines changed

3 files changed

+34
-14
lines changed

async_generator/_impl.py

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -277,6 +277,7 @@ def __init__(self, coroutine):
277277
self.ag_running = False
278278
self._finalizer = None
279279
self._closed = False
280+
self._hooks_inited = False
280281

281282
# On python 3.5.0 and 3.5.1, __aiter__ must be awaitable.
282283
# Starting in 3.5.2, it should not be awaitable, and if it is, then it
@@ -325,8 +326,8 @@ def athrow(self, type, value=None, traceback=None):
325326
return self._do_it(self._it.throw, type, value, traceback)
326327

327328
def _do_it(self, start_fn, *args):
328-
coro_state = getcoroutinestate(self._coroutine)
329-
if coro_state is CORO_CREATED:
329+
if not self._hooks_inited:
330+
self._hooks_inited = True
330331
(firstiter, self._finalizer) = get_asyncgen_hooks()
331332
if firstiter is not None:
332333
firstiter(self)
@@ -357,6 +358,9 @@ async def aclose(self):
357358
state = getcoroutinestate(self._coroutine)
358359
if state is CORO_CLOSED or self._closed:
359360
return
361+
# Make sure that even if we raise "async_generator ignored
362+
# GeneratorExit", and thus fail to exhaust the coroutine,
363+
# __del__ doesn't complain again.
360364
self._closed = True
361365
if state is CORO_CREATED:
362366
# Make sure that aclose() on an unstarted generator returns

async_generator/_tests/test_async_generator.py

Lines changed: 23 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -624,13 +624,13 @@ async def test___del__(capfd):
624624

625625
@async_generator
626626
async def awaits_when_unwinding():
627-
await yield_(1)
627+
await yield_(0)
628628
try:
629-
await yield_(2)
629+
await yield_(1)
630630
finally:
631631
await mock_sleep()
632632
try:
633-
await yield_(3)
633+
await yield_(2)
634634
finally:
635635
nonlocal completions
636636
completions += 1
@@ -640,21 +640,21 @@ async def awaits_when_unwinding():
640640
gen.__del__()
641641

642642
gen = awaits_when_unwinding()
643-
assert await collect(gen) == [1, 2, 3]
643+
assert await collect(gen) == [0, 1, 2]
644644
# Exhausted, so no problem
645645
gen.__del__()
646646

647-
for turns in (1, 2, 3):
647+
for stop_after_turn in (1, 2, 3):
648648
gen = awaits_when_unwinding()
649-
for turn in range(1, turns + 1):
649+
for turn in range(stop_after_turn):
650650
assert await gen.__anext__() == turn
651651
await gen.aclose()
652652
# Closed, so no problem
653653
gen.__del__()
654654

655-
for turns in (1, 2, 3):
655+
for stop_after_turn in (1, 2, 3):
656656
gen = awaits_when_unwinding()
657-
for turn in range(1, turns + 1):
657+
for turn in range(stop_after_turn):
658658
assert await gen.__anext__() == turn
659659

660660
if sys.implementation.name == "pypy":
@@ -665,14 +665,14 @@ async def awaits_when_unwinding():
665665
with pytest.raises(RuntimeError) as info:
666666
gen.__del__()
667667
assert "partially-exhausted async_generator" in str(info.value)
668-
if turns == 3:
668+
if stop_after_turn == 3:
669669
# We didn't increment completions, because we didn't finalize
670670
# the generator. Increment it now so the check below (which is
671671
# calibrated for the correct/CPython behavior) doesn't fire;
672672
# we know about the pypy bug.
673673
completions += 1
674674

675-
elif turns == 2:
675+
elif stop_after_turn == 2:
676676
# Stopped in the middle of a try/finally that awaits in the finally,
677677
# so __del__ can't cleanup.
678678
with pytest.raises(RuntimeError) as info:
@@ -892,7 +892,7 @@ def two(agen): # pragma: no cover
892892
with pytest.raises(TypeError):
893893
set_asyncgen_hooks(finalizer=False)
894894

895-
def in_thread(results=[]):
895+
def in_thread(results):
896896
results.append(get_asyncgen_hooks())
897897
set_asyncgen_hooks(two, one)
898898
results.append(get_asyncgen_hooks())
@@ -964,6 +964,18 @@ async def anext_verbosely(iter, ident):
964964
]
965965
del events[:]
966966

967+
# Ensure that firstiter is only called once, even if we create
968+
# two asend() coroutines before iterating either of them.
969+
iterX = agen("X")
970+
sender1 = iterX.asend(None)
971+
sender2 = iterX.asend(None)
972+
events.append("before close")
973+
sender1.close()
974+
sender2.close()
975+
await iterX.aclose()
976+
assert events == ["firstiter X", "before close"]
977+
del events[:]
978+
967979
if sys.implementation.name == "pypy":
968980
# pypy segfaults if an async generator's __del__ is called (even if it resurrects!)
969981
# and then the underlying coroutine encounters another await:

docs/source/reference.rst

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -123,7 +123,11 @@ You can use ``async_generator.set_asyncgen_hooks()`` exactly
123123
like you would use ``sys.set_asyncgen_hooks()`` with native
124124
generators. On Python 3.6+, the former is an alias for the latter,
125125
so libraries that use the native mechanism should work seamlessly
126-
with ``@async_generator`` functions.
126+
with ``@async_generator`` functions. On Python 3.5, where there is
127+
no ``sys.set_asyncgen_hooks()``, most libraries probably *won't* know
128+
about ``async_generator.set_asyncgen_hooks()``, so you'll need
129+
to exercise more care with explicit cleanup, or install appropriate
130+
hooks yourself.
127131

128132
While finishing cleanup of an async generator is better than dropping
129133
it on the floor at the first ``await``, it's still not a perfect solution;

0 commit comments

Comments
 (0)