Skip to content

feat: migrate asyncio usage to anyio across the codebase#682

Draft
raballew wants to merge 21 commits into
jumpstarter-dev:mainfrom
raballew:488-asyncio-anyio
Draft

feat: migrate asyncio usage to anyio across the codebase#682
raballew wants to merge 21 commits into
jumpstarter-dev:mainfrom
raballew:488-asyncio-anyio

Conversation

@raballew
Copy link
Copy Markdown
Member

@raballew raballew commented May 13, 2026

  • Replace asyncio primitives with anyio equivalents
  • Migrate asyncio.sleep, asyncio.create_subprocess_exec, asyncio.run, asyncio.create_task/gather, asyncio.wait_for, asyncio.Queue, asyncio.open_connection, and asyncio.get_running_loop().getaddrinfo to their anyio counterparts
  • Update test markers from @pytest.mark.asyncio to @pytest.mark.anyio and adjust mock paths accordingly
  • Files that retain asyncio imports for APIs with no anyio equivalent: InvalidStateError (gRPC), DatagramProtocol/DatagramTransport (TFTP UDP), Event/get_running_loop/new_event_loop (pysnmp),

@coderabbitai
Copy link
Copy Markdown
Contributor

coderabbitai Bot commented May 13, 2026

Important

Review skipped

Draft detected.

Please check the settings in the CodeRabbit UI or the .coderabbit.yaml file in this repository. To trigger a single review, invoke the @coderabbitai review command.

⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 25aa33a9-eea3-4f47-b932-c4ec3d7d21da

You can disable this status message by setting the reviews.review_status to false in the CodeRabbit configuration file.

Use the checkbox below for a quick retry:

  • 🔍 Trigger review
✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

raballew and others added 4 commits May 13, 2026 10:59
Replace asyncio.sleep with anyio.sleep, asyncio.create_subprocess_exec
with anyio.run_process/open_process, asyncio.Event/Queue/Task with
anyio equivalents, asyncio.run with anyio.run, and asyncio.wait_for
with anyio.fail_after/move_on_after.

Some asyncio imports remain where underlying libraries require them:
- grpc.aio raises asyncio.InvalidStateError
- pysnmp uses asyncio event loop and Event
- asyncio.DatagramProtocol for TFTP UDP server
- asyncio.get_running_loop for vsock transport

Generated-By: Forge/20260513_094204_2078695_cb9ee2d7

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
The profile parameter accepted None as a default value but was annotated
as str instead of str | None.

Generated-By: Forge/20260513_094204_2078695_cb9ee2d7
The _try_connect_and_extract_cert function had its own fail_after(timeout)
scope using the same timeout value as the outer scope in
_ssl_channel_credentials_insecure. This meant a slow first IP could
consume the entire budget, preventing other IPs from being tried. The
outer fail_after scope already enforces the total time limit.

Generated-By: Forge/20260513_094204_2078695_cb9ee2d7
The Handler._ensure_task_group entered a task group via __aenter__
but never called __aexit__, bypassing structured concurrency and
risking lost exceptions from spawned tasks. Added a done() lifecycle
method that cancels all active scopes and properly exits the task
group.

Generated-By: Forge/20260513_094204_2078695_cb9ee2d7
@raballew raballew force-pushed the 488-asyncio-anyio branch from 77aa31a to 0938967 Compare May 13, 2026 08:59
raballew and others added 17 commits May 13, 2026 11:02
Fix import sorting (I001), line length (E501), unused imports (F401),
undefined CancelledError (F821), and missing anyio.abc import for type checker.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
anyio.open_process() returns a Process object that does not have a
communicate() method. Use anyio.run_process() which accepts input and
handles stdin/stdout/stderr collection, preventing an AttributeError
at runtime when applying Jumpstarter CRs.

Generated-By: Forge/20260513_140607_2468630_62d9427a
anyio ByteReceiveStream does not have a readline() method. Wrap the
stream with BufferedByteReceiveStream and use receive_until(b"\n") for
line-delimited reading. Also adds type annotations to _read_pipe
parameters and properly closes memory object streams using async with.
Updates test mocks to use spec=ByteReceiveStream to catch API mismatches.

Generated-By: Forge/20260513_140607_2468630_62d9427a
Replace all 180 remaining @pytest.mark.asyncio markers with
@pytest.mark.anyio across 14 test files to complete the migration
from asyncio to anyio test infrastructure.

Generated-By: Forge/20260513_140607_2468630_62d9427a
Replace asyncio.subprocess.create_subprocess_exec with
anyio.open_process and use BufferedByteReceiveStream for line reading.
Update test mocks to match anyio ByteReceiveStream API and replace
asyncio.run with anyio.run in test helpers.

Generated-By: Forge/20260513_140607_2468630_62d9427a
When read_all=True, loop reading chunks until EndOfStream instead of
reading only one chunk. This matches the previous asyncio behavior
where process.stdout.read() read until EOF. Also narrows exception
handling from bare except Exception to specific anyio exceptions
(EndOfStream, ClosedResourceError).

Generated-By: Forge/20260513_140607_2468630_62d9427a
Add try/except BaseExceptionGroup to done() so that exceptions from
cancelled task group tasks are logged instead of propagating unhandled.
Document the mitmproxy addon lifecycle constraint that prevents using
a proper async with block for the task group.

Generated-By: Forge/20260513_140607_2468630_62d9427a
Rename test_timeout_captures_output to test_timeout_discards_output
to accurately reflect that anyio.run_process cancellation discards
partial output on timeout, and document this as a deliberate trade-off.

Generated-By: Forge/20260513_140607_2468630_62d9427a
sniffio is imported directly in jumpstarter.config.client but was only
available as a transitive dependency of anyio. Declare it explicitly
to prevent breakage if anyio ever changes its dependency tree.

Generated-By: Forge/20260513_140607_2468630_62d9427a
…ation

serial_asyncio is built on asyncio transports and protocols with no
anyio equivalent. Document that the test helpers intentionally use
asyncio.StreamReader/StreamWriter for this reason.

Generated-By: Forge/20260513_140607_2468630_62d9427a
Verify that exactly one final result with a returncode is produced and
that stdout data was actually received, ensuring the test exercises the
complete path through the streaming subprocess.

Generated-By: Forge/20260513_140607_2468630_62d9427a
There is no public Python API for extracting the full unverified
certificate chain. Document this dependency on CPython internals
and the incompatibility with alternative Python implementations.

Generated-By: Forge/20260513_140607_2468630_62d9427a
When the stream ends without a trailing newline, receive_until raises
IncompleteRead instead of EndOfStream. Catch IncompleteRead and flush
any remaining buffered data in the QEMU driver, and treat it as an
end-of-stream condition in the dut-network driver.

Generated-By: Forge/20260513_140607_2468630_62d9427a
Raise RuntimeError after the task group exits cleanly instead of inside
it, preventing anyio from wrapping it in a BaseExceptionGroup. Also fix
test assertions that assumed positional args for anyio.open_process
(which takes a list) and handle GeneratorExit in the early-exit test.

Generated-By: Forge/20260513_140607_2468630_62d9427a
Extract _read_stream helper from _read_process_output to reduce
cyclomatic complexity below the C901 threshold. Fix import ordering
in QEMU driver to satisfy ruff I rules.

Generated-By: Forge/20260513_140607_2468630_62d9427a
Replace bare `except Exception` with `except (anyio.EndOfStream,
anyio.ClosedResourceError)` in the shell driver streaming loop to
match the pattern used in the QEMU driver. The overly broad handler
silently swallowed real errors like RuntimeError and OSError.

Wrap the process lifecycle in try/finally to ensure the subprocess
is killed and waited on if an unexpected exception escapes the loop,
preventing zombie processes.

Generated-By: Forge/20260513_140607_2468630_62d9427a

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Add anyio_backend fixture to network driver conftest to prevent
pytest-anyio from auto-discovering trio (which is not installed).
Add ty: ignore for create_memory_object_stream subscript call that
the ty type checker misidentifies as non-callable.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant