Skip to main content

Module channel

Module channel 

Source
Expand description

An owned byte stream over a russh channel that does not close the channel when it is dropped.

§The bug this module exists to make impossible

russh’s Channel::into_stream wraps the channel in ChannelCloseOnDrop, whose Drop does

tokio::spawn(async move { sender.send(ChannelMsg::Close).await })

(russh-0.61.2/src/channels/io/mod.rs). The server hands that stream to the transport, so the Close is queued from a task nobody awaits the moment the transfer finishes, racing the exit-status the server sends next. OpenSSH frees a channel on Close and discards anything that arrives after it, so the race decided whether a client saw an exit status at all.

Measured 2026-08-03, before this module existed: a raw ssh … git-upload-pack 'team/project.git' returned a complete 244-byte advertisement and exit status 255, and git push printed * [new branch] HEAD -> main followed by error: failed to push some refs for a push that had fully succeeded server-side. gunnar-cli carried a 250 ms linger timer to invert the race; it is deleted.

§The fix, by construction (one implementation)

Channel::split hands back a ChannelReadHalf and a [ChannelWriteHalf], neither of which closes the channel on drop. The write half stays with the server, which sends exit-status and then close through the same sender the data went through, so the ordering is a property of one FIFO queue rather than of a sleep. The read half is moved in here and turned into an owned AsyncRead, because russh’s own make_reader borrows the half and the transport needs a 'static stream (it runs the blocking git protocol on spawn_blocking, which will not accept a borrow).

There is no timing dependency left anywhere on this path.

Structs§

Cancel
The word a transfer’s git work hears when its client is gone.
ChannelReader
The data band of an SSH channel, as an owned AsyncRead.
SessionBound
A channel writer that ends with an error when the session does.

Constants§

CROWDED_SESSION_END_GRACE
The grace when the transfer’s fan has others queued behind it: a worker a hung-up client holds is a worker a waiting client does not get.
SESSION_END_GRACE
How long a transfer whose session has ended is given to end by itself.

Functions§

grace_for
The grace a transfer whose session ended is given: calm, or crowded when queued operations wait for its fan.
serve_until_session_ends
A transfer, bound to its session: what serve returns, unless the session ends first and the transfer does not end by itself within grace() (asked when the session ends; grace_for — the server gives SESSION_END_GRACE, or CROWDED_SESSION_END_GRACE when others wait for the fan), in which case it ends with ended().
session_ended_error
The error a transfer gets when the client’s session ended under it.
session_over
Resolve once over says the session has ended (or its sender is gone, which is the same thing said by Drop).

Type Aliases§

SessionOver
The session is over, as a value every transfer on it can watch.