Skip to content
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

Fixed inability to start tasks from async_generator_asend objects on asyncio #675

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all 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
1 change: 1 addition & 0 deletions docs/versionhistory.rst
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ This library adheres to `Semantic Versioning 2.0 <http://semver.org/>`_.
- Fixed ``Process.stdin.aclose()``, ``Process.stdout.aclose()``, and
``Process.stderr.aclose()`` not including a checkpoint on asyncio (PR by Ganden
Schaffner)
- Fixed inability to start tasks from ``async_generator_asend`` objects on asyncio

**4.2.0**

Expand Down
6 changes: 2 additions & 4 deletions src/anyio/_backends/_asyncio.py
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@
)
from asyncio.base_events import _run_until_complete_cb # type: ignore[attr-defined]
from collections import OrderedDict, deque
from collections.abc import AsyncIterator, Generator, Iterable
from collections.abc import AsyncIterator, Coroutine, Generator, Iterable
from concurrent.futures import Future
from contextlib import suppress
from contextvars import Context, copy_context
Expand All @@ -28,7 +28,6 @@
CORO_RUNNING,
CORO_SUSPENDED,
getcoroutinestate,
iscoroutine,
)
from io import IOBase
from os import PathLike
Expand All @@ -45,7 +44,6 @@
Callable,
Collection,
ContextManager,
Coroutine,
Mapping,
Optional,
Sequence,
Expand Down Expand Up @@ -741,7 +739,7 @@ def task_done(_task: asyncio.Task) -> None:
parent_id = id(self.cancel_scope._host_task)

coro = func(*args, **kwargs)
if not iscoroutine(coro):
if not isinstance(coro, Coroutine):
Copy link
Collaborator

Choose a reason for hiding this comment

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

the above comment is about async_generator_asend specifically, but in general it seems buggy to replace inspect.iscoroutine here with either (a) abc.Coroutine.__instancecheck__ or (b) asyncio.iscoroutine.

the problem is that anything that AnyIO allows past this check needs to support inspect.getcoroutinestate (because of _task_started), but:

  • (a) and (b) would allow through coroutines/coroutine-likes that don't support getcoroutinestate, such as types that implement the abc.Coroutine interface but are not inspectable (e.g. async_generator_asend) and types that a user or a third-party library calls abc.Coroutine.register on.

  • (b) would also allow through "coroutine-likes" that aren't even coroutine-like, such as every generator on Python < 3.12.

Copy link
Owner Author

Choose a reason for hiding this comment

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

Thank you for the in-depth analysis. In this light, wrapping the async_generator_asend object in a coroutine might be less of a hassle. I need to consider my options here.

prefix = f"{func.__module__}." if hasattr(func, "__module__") else ""
raise TypeError(
f"Expected {prefix}{func.__qualname__}() to return a coroutine, but "
Expand Down
16 changes: 16 additions & 0 deletions tests/test_taskgroups.py
Original file line number Diff line number Diff line change
Expand Up @@ -1336,6 +1336,22 @@ async def wait_cancel() -> None:
await cancelled.wait()


async def test_start_soon_from_asend() -> None:
started = False

async def genfunc() -> AsyncGenerator[None, None]:
nonlocal started
started = True
yield

generator = genfunc()
async with anyio.create_task_group() as task_group:
task_group.start_soon(generator.asend, None)

assert started
await generator.aclose()


class TestTaskStatusTyping:
"""
These tests do not do anything at run time, but since the test suite is also checked
Expand Down
Loading