You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
paramiko's SFTPClient.get() with its default prefetch hangs indefinitely when downloading from bssh-server, while the same code completes in 0.1 s against OpenSSH sshd on the same host. This is NOT a regression from PR #224: it reproduces identically on pre-#224 (fbbf226) and post-#224 (e4f2810) builds. Additionally, even strictly sequential reads (prefetch=False) complete but take ~37 ms per 32 KiB request (8 MiB in 9.4 s, about 0.85 MiB/s), which points at a response-flush delay on the server read path and is likely the same root cause. Uploads via paramiko are unaffected (8 MiB in 0.2 s). paramiko is the SSH stack under Ansible and many Python tools, so this has real-world impact.
Environment
bssh-server built from main at e4f2810 (also reproduced with fbbf226), russh 0.62.1
Loopback 127.0.0.1 on Linux 6.17 (ARM), files on tmpfs
Secondary cosmetic finding: when the paramiko session closes, the server logs ERROR bssh::server: Session error error=Connection reset by peer (os error 104); an ordinary client disconnect should not log at ERROR level.
Analysis
Hypothesis (not yet confirmed): paramiko's prefetch() fires SSH_FXP_READ requests for the entire file immediately without bounding the number of outstanding requests (unlike OpenSSH's sftp, which caps outstanding requests at 64, and sshj, which uses bounded read-ahead). For an 8 MiB file that is a burst of 256 READ requests with distinct offsets on one channel. The server's read-response path stalls under that burst.
The ~37 ms per request observed even in the strictly sequential case suggests READ responses are not flushed promptly (a deferred flush or missing flush on the response path); under the burst pattern, delayed responses plus SSH channel window backpressure can deadlock: the client stops growing the window because no data arrives, and the server never flushes what it has.
Investigation should start in the SFTP server request loop in crates/bssh-russh-sftp/src/server/mod.rs (both the current pipelined loop and the logic it inherited) and the interaction with russh channel window handling on the tx path.
Acceptance Criteria
paramiko get() with default prefetch completes with byte-for-byte integrity for 8 MiB and for a multi-GiB file.
Sequential reads (prefetch=False) no longer exhibit the ~37 ms-per-request stall (loopback per-request latency should be sub-millisecond).
A regression test covers bursted/out-of-order READ requests (either a Rust-side test simulating the paramiko prefetch pattern, or the paramiko interop script from tools/bench once merged).
No regressions for OpenSSH sftp and sshj clients (up/down, integrity).
(Secondary) client-initiated disconnects no longer log at ERROR level.
Summary
paramiko's
SFTPClient.get()with its default prefetch hangs indefinitely when downloading frombssh-server, while the same code completes in 0.1 s against OpenSSH sshd on the same host. This is NOT a regression from PR #224: it reproduces identically on pre-#224 (fbbf226) and post-#224 (e4f2810) builds. Additionally, even strictly sequential reads (prefetch=False) complete but take ~37 ms per 32 KiB request (8 MiB in 9.4 s, about 0.85 MiB/s), which points at a response-flush delay on the server read path and is likely the same root cause. Uploads via paramiko are unaffected (8 MiB in 0.2 s). paramiko is the SSH stack under Ansible and many Python tools, so this has real-world impact.Environment
bssh-serverbuilt frommainate4f2810(also reproduced withfbbf226), russh 0.62.1Reproduction
Against OpenSSH sshd on the same host, the identical script completes
putin 0.2 s andgetin 0.1 s.Evidence
open(b'...', 'rb') -> <handle>; no READ responses ever arrive at the client (with default prefetch).sftpCLI and sshj 0.38.0 both upload and download multi-GiB files against the same server without stalls and with byte-for-byte integrity (verified during test(sftp): third-party client interop validation for the raised channel sizing and pipelined write path (FileZilla, WinSCP, sshj/Cyberduck) #226 interop validation), so the hang is specific to the request pattern paramiko uses.ERROR bssh::server: Session error error=Connection reset by peer (os error 104); an ordinary client disconnect should not log at ERROR level.Analysis
Hypothesis (not yet confirmed): paramiko's
prefetch()fires SSH_FXP_READ requests for the entire file immediately without bounding the number of outstanding requests (unlike OpenSSH's sftp, which caps outstanding requests at 64, and sshj, which uses bounded read-ahead). For an 8 MiB file that is a burst of 256 READ requests with distinct offsets on one channel. The server's read-response path stalls under that burst.The ~37 ms per request observed even in the strictly sequential case suggests READ responses are not flushed promptly (a deferred flush or missing flush on the response path); under the burst pattern, delayed responses plus SSH channel window backpressure can deadlock: the client stops growing the window because no data arrives, and the server never flushes what it has.
Investigation should start in the SFTP server request loop in
crates/bssh-russh-sftp/src/server/mod.rs(both the current pipelined loop and the logic it inherited) and the interaction with russh channel window handling on the tx path.Acceptance Criteria
get()with default prefetch completes with byte-for-byte integrity for 8 MiB and for a multi-GiB file.prefetch=False) no longer exhibit the ~37 ms-per-request stall (loopback per-request latency should be sub-millisecond).Related
fbbf226)