Skip to content

feat(rsync_io): ChannelReader/Writer AsyncRead/AsyncWrite adapters (#1797)#4271

Merged
oferchen merged 1 commit into
masterfrom
feat/channel-async-adapters-1797
May 17, 2026
Merged

feat(rsync_io): ChannelReader/Writer AsyncRead/AsyncWrite adapters (#1797)#4271
oferchen merged 1 commit into
masterfrom
feat/channel-async-adapters-1797

Conversation

@oferchen
Copy link
Copy Markdown
Owner

Summary

  • Adds ChannelReader and ChannelWriter in a new crates/rsync_io/src/channel_adapter.rs module that bridge tokio::sync::mpsc::{Receiver, Sender}<Vec<u8>> to tokio::io::AsyncRead / AsyncWrite.
  • Adds a new async-ssh feature on rsync_io. The module is fully gated behind this feature, so default builds are unchanged.
  • Exposes pair(capacity) returning two cross-connected (ChannelReader, ChannelWriter) halves for in-memory duplex streams.

Why

Companion to the in-flight async SSH transport (task #1796). The adapters give the async transport a way to expose its stdio over an in-process duplex channel while still satisfying APIs that require raw async stream traits.

Implementation notes

  • ChannelReader drains a one-time buffered: Vec<u8> slice before pulling the next chunk from the channel, so oversized chunks are served across multiple poll_read calls without dropping bytes.
  • ChannelWriter first tries try_send, then falls back to reserve_owned(). The reservation future is stored on the writer so the channel's wait-queue registration survives across poll_write calls and the task is woken when capacity frees up.
  • poll_flush is a no-op (the channel itself provides ordering). poll_shutdown drops the sender, surfacing EOF on the read side.
  • A closed receiver surfaces on the writer as io::ErrorKind::BrokenPipe.
  • Uses Vec<u8> rather than bytes::Bytes since bytes is not a workspace dependency.

Test plan

  • cargo fmt --all clean
  • CI fmt + clippy
  • CI nextest (stable) - covers the new unit tests when the async-ssh feature is enabled
  • CI Windows / macOS / Linux musl (pure tokio, cross-platform)

Unit tests (gated cfg(test) under the async-ssh feature)

  • round_trip_bytes - write then read N bytes.
  • oversized_chunk_is_drained_across_reads - reader serves an over-large chunk across multiple poll_read calls.
  • backpressure_blocks_until_reader_drains - bounded capacity 1 forces the second write to suspend until the reader pulls the first message.
  • dropping_writer_yields_eof - dropping the writer gives reader Ok(0).
  • shutdown_yields_eof - explicit shutdown() after a write still delivers buffered data, then EOF.
  • write_after_shutdown_is_broken_pipe - subsequent writes after shutdown return BrokenPipe.
  • write_to_closed_reader_is_broken_pipe - dropping the receiver surfaces BrokenPipe to the writer.
  • duplex_pair_exchanges_data_both_ways - pair() gives a working bidirectional in-memory stream.

…1797)

Bridge `tokio::sync::mpsc` channel pairs to `tokio::io::AsyncRead` and
`AsyncWrite` so async transports (for example, the in-flight async SSH
transport in #1796) can be composed with traits that expect raw async
streams.

The new `channel_adapter` module is gated behind a new `async-ssh`
feature so default builds are unaffected. `pair(capacity)` builds two
cross-connected halves for a fully in-memory duplex stream.

Unit tests cover round-trip, oversized-chunk re-draining, backpressure,
EOF on writer drop, EOF on explicit shutdown, broken-pipe surfacing on
both writer-side and reader-side closes, and bidirectional `pair()`
exchange.
@oferchen oferchen merged commit d83e92d into master May 17, 2026
12 of 14 checks passed
@github-actions github-actions Bot added the enhancement New feature or request label May 17, 2026
oferchen added a commit that referenced this pull request May 18, 2026
…1797) (#4271)

Bridge `tokio::sync::mpsc` channel pairs to `tokio::io::AsyncRead` and
`AsyncWrite` so async transports (for example, the in-flight async SSH
transport in #1796) can be composed with traits that expect raw async
streams.

The new `channel_adapter` module is gated behind a new `async-ssh`
feature so default builds are unaffected. `pair(capacity)` builds two
cross-connected halves for a fully in-memory duplex stream.

Unit tests cover round-trip, oversized-chunk re-draining, backpressure,
EOF on writer drop, EOF on explicit shutdown, broken-pipe surfacing on
both writer-side and reader-side closes, and bidirectional `pair()`
exchange.
oferchen added a commit that referenced this pull request May 18, 2026
…1797) (#4271)

Bridge `tokio::sync::mpsc` channel pairs to `tokio::io::AsyncRead` and
`AsyncWrite` so async transports (for example, the in-flight async SSH
transport in #1796) can be composed with traits that expect raw async
streams.

The new `channel_adapter` module is gated behind a new `async-ssh`
feature so default builds are unaffected. `pair(capacity)` builds two
cross-connected halves for a fully in-memory duplex stream.

Unit tests cover round-trip, oversized-chunk re-draining, backpressure,
EOF on writer drop, EOF on explicit shutdown, broken-pipe surfacing on
both writer-side and reader-side closes, and bidirectional `pair()`
exchange.
@oferchen oferchen deleted the feat/channel-async-adapters-1797 branch May 19, 2026 19:28
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

enhancement New feature or request

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant