ConnectionInfo.SendTimeout — configurable socket send timeout to prevent indefinite hangs on TCP zero-window stalls
Nobody has claimed this yet.
- Dominant language
- C#
- Stars
- 4.4k
- Forks
- 993
- Avg merge
- 9d 21h
- Merged PRs (30d)
- 1
Description
Problem
When uploading files over SFTP to a slow or unresponsive server, Session.SendPacket() can hang indefinitely with no way to recover.
The root cause is in SocketAbstraction.Send():
var bytesSent = socket.Send(data, offset + totalBytesSent, totalBytesToSend - totalBytesSent, SocketFlags.None);
socket.Send() is a blocking call and Socket.SendTimeout is never set, so it defaults to 0 (infinite). When the server's TCP receive window drops to zero (the server is alive but not consuming data), the OS
holds the call open indefinitely — no exception, no return. The standard TCP dead-peer timeout (several minutes) only applies when the server is completely unreachable, not in the zero-window scenario.
This is distinct from the channel-level window wait in Channel.cs, which already has a 30-second timeout via ConnectionInfo.Timeout. That protection only covers the SSH protocol layer; it never gets a chance
to fire because execution is stuck in socket.Send() first.
OperationTimeout (on SftpClient) similarly cannot help — it guards the wait for an SFTP protocol response after a packet has been sent, not the send itself.
Proposed fix
Add SendTimeout to ConnectionInfo (default Timeout.InfiniteTimeSpan to preserve existing behaviour) and apply it to the socket immediately after connection:
// ConnectionInfo.cs
private TimeSpan _sendTimeout = System.Threading.Timeout.InfiniteTimeSpan;
public TimeSpan SendTimeout
{
get => _sendTimeout;
set
{
value.EnsureValidTimeout(nameof(SendTimeout));
_sendTimeout = value;
}
}
// Session.cs — both Connect() and ConnectAsync() paths
_socket = _serviceFactory.CreateConnector(ConnectionInfo, _socketFactory)
.Connect(ConnectionInfo);
_socket.SendTimeout = ConnectionInfo.SendTimeout.AsTimeout();
When SendTimeout elapses, socket.Send() throws SocketException with SocketError.TimedOut, which propagates out of SendPacket() and terminates the session normally.
Notes
- Default is infinite — no behaviour change for existing users
- The timeout is a stall detector, not a total-transfer budget: it resets on every successful send call, so large files over slow-but-healthy connections are not affected
- Applies to all SSH traffic (not just SFTP), which is correct — a stalled send on any packet type means the session is broken
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start with ConnectionInfo.cs to review existing timeout validation, then inspect the socket setup in both Connect() and ConnectAsync() in Session.cs. Add the configurable send-timeout behavior described in the issue, preserving the infinite default, and verify that a stalled send times out while existing connections retain their behavior.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- csharp
- Domain
- networking
- Issue type
- Bug
- Difficulty
- 3/5
- Estimated time
- 1-2 days
- Activity status
- Quiet
- Clarity
- Clearly specified
- Newbie friendliness
- 70/100