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
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():
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:
When SendTimeout elapses, socket.Send() throws SocketException with SocketError.TimedOut, which propagates out of SendPacket() and terminates the session normally.
Notes