Skip to content

chore(bssh-russh-sftp): sync vendored fork to upstream 2.3.0, drop write wrapper#211

Closed
Yaminyam wants to merge 3 commits into
lablup:mainfrom
Yaminyam:chore/bump-russh-sftp-vendored-to-2.3.0
Closed

chore(bssh-russh-sftp): sync vendored fork to upstream 2.3.0, drop write wrapper#211
Yaminyam wants to merge 3 commits into
lablup:mainfrom
Yaminyam:chore/bump-russh-sftp-vendored-to-2.3.0

Conversation

@Yaminyam

Copy link
Copy Markdown
Member

Summary

Sync the vendored bssh-russh-sftp crate up to upstream russh-sftp 2.3.0. The fork's previous deltas have either landed upstream or been narrowed to a single helper that's now in review.

Concern Status
serde_bytes perf fix for WRITE/DATA (~+29% on large transfers) ✅ Upstream — AspectUnk/russh-sftp#83, in 2.3.0
AsyncWrite for File pipelined (dynamic chunk sizes, write_acks queue) ✅ Upstream — AspectUnk/russh-sftp#85, in 2.3.0
File::read_to_writer_pipelined (high-RTT read helper) 🟡 Under review — AspectUnk/russh-sftp#91

AsyncRead for File is still sequential upstream (one READ per poll_read, wait for reply), so we keep the read-side helper in the vendored crate until #91 merges. Everything else this fork carried is gone.

What changed

  • crates/bssh-russh-sftp/ — vendored src/ re-synced to upstream master (2.3.0 = dcc0c06 bump to 2.3.0), then the read_to_writer_pipelined helper added on top of upstream File. Cargo.toml updated to match upstream deps (notably dashmap replaces our flurry). README + scripts refreshed to reflect the simplified state. patches/sftp-serde-bytes-perf.patch deleted (obsolete).
  • Root Cargo.toml — pin bumped 2.1.1 → 2.3.0, comment refreshed.
  • src/ssh/tokio_client/file_transfer.rsfile.write_all_pipelined(reader, 64).await calls replaced with tokio::io::copy(&mut reader, &mut file).await now that AsyncWrite is natively pipelined upstream. We plumb the 64-deep pipeline depth through SftpSession::new_with_config(stream, sftp_config()) (Config::max_concurrent_writes = 64), since upstream's default is 8.
  • src/server/sftp.rs — add impl From<SftpError> for StatusReply to satisfy upstream PR Terminal escape sequences displayed on screen when running Neovim in SSH session #76's tightened Handler::Error: Into<StatusReply> bound. Existing impl From<SftpError> for StatusCode kept; both coexist.

Why

  • Reduces what this repo has to maintain (fewer wrappers, no patches).
  • Future upgrades from 2.3.0 → 2.4.x and beyond become a straight sync-upstream.sh master instead of a patch-rebase exercise.
  • When AspectUnk/russh-sftp#91 merges and a release goes out, we can drop the vendored fork entirely.

Test plan

  • cargo build → clean
  • cargo build -p bssh-russh-sftp → clean
  • cargo test --lib -p bssh-russh-sftp → 0/0 (baseline unchanged)
  • cargo test --lib → existing tests pass
  • Manual SFTP round-trip (upload + download against an interactive sshd) — recommend running before merge to confirm Config::max_concurrent_writes plumbing works end-to-end on a real network.

Related

Yaminyam and others added 3 commits May 6, 2026 17:53
The high-level `AsyncWrite`/`AsyncRead` impls on `File` issue exactly
one SFTP `WRITE`/`READ` at a time and `await` its `STATUS`/`DATA` reply
before sending the next. Sustained throughput is therefore bounded by
`chunk_size / RTT` — at 50 ms RTT with the default 256 KiB chunk that
caps a single transfer at ~5 MiB/s no matter how fast the link is.

Add two pipelined helpers on `File` that keep up to N SFTP requests in
flight concurrently, mirroring how OpenSSH's `sftp(1)` client behaves
(`-R 64` by default):

* `File::write_all_pipelined<R: AsyncRead>(reader, max_inflight)` —
  reads chunks from `reader` and dispatches `session.write(...)` futures
  via `FuturesUnordered`, refilling the pipeline as in-flight writes
  complete. Memory bounded by `max_inflight * write_len`.
* `File::read_to_writer_pipelined<W: AsyncWrite>(writer, max_inflight)` —
  symmetric for downloads. Out-of-order responses are buffered in a
  `BTreeMap` keyed by offset and flushed to `writer` as soon as the
  next-expected chunk arrives.

Wire `Client::upload_file`/`download_file`/`upload_dir_recursive`/
`download_dir_recursive` to use the new helpers with `MAX_INFLIGHT_REQUESTS = 64`.

Measured on macOS arm64 against `bssh-server` v2.1.3 on loopback with a
1 GiB file:

| op       | build                  | real    | RSS      |
|----------|------------------------|---------|----------|
| upload   | vanilla v2.1.3         | 39.30s  | 3.23 GB  |
| upload   | streaming-only         |  3.47s  |   20 MB  |
| upload   | streaming + pipelined  |  2.27s  |   49 MB  |
| download | vanilla v2.1.3         |  3.93s  | 2.17 GB  |
| download | streaming-only         |  3.41s  |   16 MB  |
| download | streaming + pipelined  |  1.34s  |  288 MB  |

Pipelining adds ~+53% on upload and ~+155% on download throughput on top
of the streaming patch (which already eliminated the whole-file load).
Peak RSS stays well below the unpatched levels: download holds at most
~`max_inflight` chunks pending in the reorder map, and upload caps at
`max_inflight * chunk_size + reader buffer`.
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
…apper

Upstream russh-sftp 2.3.0 picked up both perf landings that motivated this
fork's prior delta:

- AspectUnk/russh-sftp#83 — `serde_bytes` perf for `WRITE`/`DATA` payloads
  (the original reason for `sftp-serde-bytes-perf.patch`).
- AspectUnk/russh-sftp#85 — pipelined `AsyncWrite for File` with a
  `write_acks` queue and dynamic chunk sizing, plus
  `Config::max_concurrent_writes`. This subsumes our `write_all_pipelined`
  helper.

The only remaining local delta is the read-side helper
`File::read_to_writer_pipelined`, which is now proposed upstream as
AspectUnk/russh-sftp#91. Until that merges and lands in a release, we keep
it in the vendored crate.

Changes:
- `crates/bssh-russh-sftp/src/` synced to upstream `master` (2.3.0), then
  the `read_to_writer_pipelined` helper added on top of upstream `File`.
- `crates/bssh-russh-sftp/Cargo.toml` mirrors upstream deps (notably
  `dashmap` replaced our `flurry`, plus pinned point versions).
- `patches/sftp-serde-bytes-perf.patch` removed (obsolete).
- `sync-upstream.sh` / `create-patch.sh` simplified — no longer reapply the
  obsolete patch.
- `src/ssh/tokio_client/file_transfer.rs`: drop `write_all_pipelined`
  callers in favor of plain `tokio::io::copy`, and plumb our 64-deep
  pipeline through `SftpSession::new_with_config(stream, sftp_config())`
  (was: implicit default of 8).
- `src/server/sftp.rs`: add `impl From<SftpError> for StatusReply` to
  satisfy upstream PR lablup#76's tightened `Handler::Error: Into<StatusReply>`
  bound.

When lablup#91 merges and a russh-sftp release goes out, drop the vendored fork
entirely and consume upstream directly.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
@Yaminyam

Copy link
Copy Markdown
Member Author

Closing — fork is already cleaned up upstream.

#207 (merged 2026-05-25 by @inureyes) did the same vendored sync work this PR proposes, and more:

  • bssh-russh-sftp/src/ resynced to upstream 2.3.0 (the part this PR was trying to do)
  • channel-write-ordering.patch, sha1-mac-exclude.patch, agent-frame-length-cap.patch all dropped (upstreamed)
  • pipelined-file-io.patch introduced as the proper carrier for the read-side helper (the same one I sent upstream as feat(client): add File::read_to_writer_pipelined for high-RTT SFTP reads AspectUnk/russh-sftp#91)
  • crates/bssh-russh-sftp/src/client/runtime.rs + server/reply.rs already pulled in

I opened this against an outdated main on my fork without realizing #207 had already happened. Once AspectUnk/russh-sftp#91 lands in a russh-sftp release, the only remaining cleanup is dropping pipelined-file-io.patch and bssh-russh-sftp entirely.

Sorry for the noise.

@Yaminyam Yaminyam closed this Jun 23, 2026
@Yaminyam Yaminyam deleted the chore/bump-russh-sftp-vendored-to-2.3.0 branch June 23, 2026 08:48
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