fix(server): drain Handle::data backlog before select! - #729
Closed
Yaminyam wants to merge 1 commit into
Closed
Conversation
`tokio::select!` in the server session loop does not always wake up
promptly for messages produced by spawned tasks via `Handle::data()`
when the channel is being filled faster than the select cycle reads
from it. In interactive PTY sessions where a separate task is pumping
shell output, this manifests as the client missing chunks of output
until the next event happens to wake select.
Before entering `select!`, drain at most 64 pending messages from the
session receiver with `try_recv()`, dispatching them to the appropriate
`Session::{data,extended_data,eof,close,channel_success,channel_failure,...}`
handlers. The 64-message cap ensures select! still gets a chance to run
between batches so client input (e.g. Ctrl+C) stays responsive.
This is the same fix bssh has been carrying as
`crates/bssh-russh/patches/handle-data-fix.patch`. Its README pins at
`inureyes/russh:fix/handle-data-from-spawned-tasks` and notes the fork
will be dropped once it merges upstream — this PR is the upstream half
of that contract. Original author of the fix: @inureyes.
Co-authored-by: Jeongkyu Shin <inureyes@gmail.com>
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Owner
|
Thanks for bringing it to my attention - I've added my own cleaner fix in #731 @all-contributors please add @inureyes for code |
Contributor
|
I've put up a pull request to add @inureyes! 🎉 |
Eugeny
added a commit
that referenced
this pull request
Jul 2, 2026
Eugeny
pushed a commit
that referenced
this pull request
Jul 3, 2026
Adds @inureyes as a contributor for code. This was requested by Eugeny [in this comment](#729 (comment)) [skip ci] --------- Co-authored-by: allcontributors[bot] <46447321+allcontributors[bot]@users.noreply.github.com>
This was referenced Jul 27, 2026
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
tokio::select!in the server session loop doesn't always wake up promptly for messages produced by spawned tasks viaHandle::data()when the internal mpsc channel is being filled faster than the select cycle drains it. In interactive PTY servers where a separate task pumps shell output throughHandle::data(), this shows up as the client missing chunks of output until some other event happens to wake select.This PR drains the receiver with
try_recv()before enteringselect!, with a 64-message cap so select still gets to run between batches and client input (Ctrl+C, window resize, …) stays responsive.Reproduction
Any russh-based SSH server with a PTY session whose shell output is generated from a spawned task pushing via
Handle::data()faster than the session loop's select cycle reads from its mpsc channel — e.g.yes | head -n 100000over an interactive PTY. The client sees periodic stalls / dropped output until further activity.Change
In
run_inner(server session loop), before the existingtokio::select!, batch-drain up toMAX_MESSAGES_PER_BATCH = 64messages fromself.receiverwithtry_recv()and dispatch through the same handlersselect!would have used:data/extended_data/eof/close/channel_success/channel_failure/xon_xoff_request/exit_status_request/exit_signal_request/WindowAdjusted/ChannelOpen{Agent,Session,DirectTcpip,...}. Gated on!self.kex.active()so the rekey state machinery isn't perturbed.The cap exists so high-throughput output can't starve client input — once a batch is full we fall through to
select!, which picks up any client-side event (input, channel close, keepalive timeout) before the next batch.Origin
This is the same fix bssh has been carrying as
crates/bssh-russh/patches/handle-data-fix.patch. Its README pins atinureyes/russh:fix/handle-data-from-spawned-tasksand notes "When merged upstream, this fork will be deprecated" — this PR is the upstream half of that contract. Original author of the fix: @inureyes (bssh maintainer).Test plan
cargo build -p russh→ cleanHandle::data()server, confirm no output stalls underyes | head -n 100000-style load.