runtime: Suggestion: Add WebSocketOptions.Timeout to ClientWebSocket
Edited by @CarnaViire
API Proposal:
public class WebSocketCreationOptions
{
public TimeSpan KeepAliveInterval { get; set; } // existing
+ public TimeSpan KeepAliveTimeout { get; set; } = TimeSpan.Zero;
}
public class ClientWebSocketOptions
{
public TimeSpan KeepAliveInterval { get; set; } // existing
+ public TimeSpan KeepAliveTimeout { get; set; } = TimeSpan.Zero;
}
Usage:
For ClientWebSocket:
var ws = new ClientWebSocket();
ws.Options.KeepAliveTimeout = TimeSpan.FromSeconds(10);
await ws.ConnectAsync(uri, cts.Token);
For ManagedWebSocket:
var options = new WebSocketCreationOptions()
{
KeepAliveInterval = WebSocket.DefaultKeepAliveInterval,
KeepAliveTimeout = TimeSpan.FromSeconds(10)
};
var ws = WebSocket.CreateFromStream(stream, options);
Notes on behavior:
Timeout.InfiniteTimeSpan or TimeSpan.Zero would disable the timeout logic and fall back to existing “unsolicited” Pong keep-alive (per understanding in https://github.com/dotnet/runtime/issues/35473#issuecomment-620726903 we cannot send Pings without waiting for the reply)
Any other positive timeout value would turn on Ping-Pong logic; we will be sending Pings instead of Pongs and track whether we’ve received Pongs in time; if not, the connection is treated as stale and aborted. The behavior and implementation should be similar to HTTP/2 KeepAlivePingTimeout.
For justification, see original issue below
Original issue by @bjornen77
I think that adding a WebSocketOptions.Timeout property would be a good thing.
Today, the ClientWebSocket is closed when the TCP Retransmission times out(if the connection to the server drops for some reason). This timeout seems to be different on Windows and Linux. By default, on Windows it takes about 21 seconds and on Linux it takes about 15 minutes before the WebSocket is closed. It would be good if we could have a separate Timeout setting, so that we get the same behavior regardless of the platform. And also that this could be configured by the application without having to modify the TCP Timeout settings in the OS… The websocket should still be disconnected if the OS TCP Timeout is reached.
The ClientWebSocket today only sends “Pong” to the server, and therefore it does not wait for a “Pong” response to arrive. I think that the ClientWebSocket shall send a “Ping” instead, and then verify that a “Pong” is received within the Timeout set. If not received, the connection shall be closed. Or at least it should expose the "received “Pong” message in the WebSocketReceiveResult.MessageType. It would be good if an received “Ping” message from a server is also exposed. Then the application could use these Ping/Pong message types to implement its own timeout logic.
About this issue
- Original URL
- State: open
- Created 3 years ago
- Reactions: 16
- Comments: 17 (12 by maintainers)
Strong +1 for this issue. This is an important feature for applications like games where detecting a broken connection quickly is important for user experience. The WebSocket API does not expose the ability to send ping or pong messages, so there isn’t even really a way to add this functionality in our applications without creating our own works-like ping/pong messages (which aren’t really ping/pong messages but regular websocket messages).
Just one point of clarification in the original issue — OP mentions that the current implementation only sends pong, rather than ping. I don’t think that’s true, from what I can see in ManagedWebSocket:518. I think the issue is simply that the implementation doesn’t wait for a pong in response to the ping.
The WebSocket RFC does not explicitly specify that clients should disconnect in the case a pong is not received in response to a ping within a certain timeframe. I think this would be a good thing to do though, as a user-configurable option. It should probably be default-off, so the change doesn’t impact any applications which are somehow depending on very long retry times. Just adding an extra
WebSocketOptionsfor this as OP suggests is the right approach IMHO.WebSocketAcceptContextwould likely need a new property, something likeKeepAliveTimeout. I can help out if this feature gets added.I’ve updated the root description with the API proposal
I guess SignalR uses its own pingpong, and is covering over for this flaw for most use cases? But I use it with vs-streamjsonrpc and ended up implementing my own pingpong, and used 99.9% time on that and the rest on actual work… My testing involved killing processes, pulling network cables, supending/resuming/killing VM’s, “pulling” network “cables” within VM’s etc. I easily got into situations with infinite hang (waited over night).
Not so. Issue https://github.com/dotnet/aspnetcore/issues/24614 is already asking for the same (and duplicate https://github.com/dotnet/aspnetcore/issues/26117)