-
Notifications
You must be signed in to change notification settings - Fork 5.4k
Add WebSocket Keep-Alive Ping and Timeout (minimal) implementation #105841
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from 4 commits
Commits
Show all changes
14 commits
Select commit
Hold shift + click to select a range
dd9de54
Add Keep-Alive Ping and Timeout implementation
CarnaViire b1a30b7
Minor fixes and add ClientWebSocket tests
CarnaViire 5f30316
Expand KeepAliveTest_Loopback
CarnaViire 9c9312d
Update WebSocketCreationOptions.cs
rzikm 9854c63
Address code review feedback
rzikm 42ba661
More feedback
rzikm eeae320
add lock and some debug logs
CarnaViire 3641d86
more debug logs
CarnaViire 1803be8
fix test
CarnaViire a4c5bcf
Address Feedback
CarnaViire 83d88e4
TO REVERT: run new tests in innerloop
CarnaViire 5698eac
Revert "TO REVERT: run new tests in innerloop"
CarnaViire 9ee4221
add tripleslash docs
CarnaViire 9ba122b
Merge branch 'main' into ws-timeout-v4
CarnaViire File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
19 changes: 19 additions & 0 deletions
19
src/libraries/Common/src/System/Net/WebSockets/WebSocketDefaults.cs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,19 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Threading; | ||
|
|
||
| namespace System.Net.WebSockets | ||
| { | ||
| /// <summary> | ||
| /// Central repository for default values used in WebSocket settings. Not all settings are relevant | ||
| /// to or configurable by all WebSocket implementations. | ||
| /// </summary> | ||
| internal static partial class WebSocketDefaults | ||
| { | ||
| public static readonly TimeSpan DefaultKeepAliveInterval = TimeSpan.Zero; | ||
| public static readonly TimeSpan DefaultClientKeepAliveInterval = TimeSpan.FromSeconds(30); | ||
|
|
||
| public static readonly TimeSpan DefaultKeepAliveTimeout = Timeout.InfiniteTimeSpan; | ||
| } | ||
| } |
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
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
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
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
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
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
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
155 changes: 155 additions & 0 deletions
155
src/libraries/System.Net.WebSockets.Client/tests/KeepAliveTest.Loopback.cs
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
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,155 @@ | ||
| // Licensed to the .NET Foundation under one or more agreements. | ||
| // The .NET Foundation licenses this file to you under the MIT license. | ||
|
|
||
| using System.Collections.Generic; | ||
| using System.Net.Test.Common; | ||
| using System.Threading; | ||
| using System.Threading.Tasks; | ||
|
|
||
| using Xunit; | ||
| using Xunit.Abstractions; | ||
|
|
||
| namespace System.Net.WebSockets.Client.Tests | ||
| { | ||
| [SkipOnPlatform(TestPlatforms.Browser, "KeepAlive not supported on browser")] | ||
| public abstract class KeepAliveTest_Loopback : ClientWebSocketTestBase | ||
| { | ||
| public KeepAliveTest_Loopback(ITestOutputHelper output) : base(output) { } | ||
|
|
||
| protected virtual Version HttpVersion => Net.HttpVersion.Version11; | ||
|
|
||
| public static readonly object[][] UseSsl_MemberData = PlatformDetection.SupportsAlpn | ||
| ? new[] { new object[] { false }, new object[] { true } } | ||
| : new[] { new object[] { false } }; | ||
|
|
||
| [Theory] | ||
| [MemberData(nameof(UseSsl_MemberData))] | ||
| public Task KeepAlive_LongDelayBetweenSendReceives_Succeeds(bool useSsl) | ||
| { | ||
| var clientMsg = new byte[] { 1, 2, 3, 4, 5, 6 }; | ||
| var serverMsg = new byte[] { 42 }; | ||
| var clientAckTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); | ||
| var serverAckTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); | ||
| var longDelayByServerTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); | ||
| TimeSpan LongDelay = TimeSpan.FromSeconds(10); | ||
|
|
||
| var timeoutCts = new CancellationTokenSource(TimeOutMilliseconds); | ||
|
|
||
| var options = new LoopbackWebSocketServer.Options(HttpVersion, useSsl, GetInvoker()) | ||
| { | ||
| DisposeServerWebSocket = true, | ||
| DisposeClientWebSocket = true, | ||
| ConfigureClientOptions = clientOptions => | ||
| { | ||
| clientOptions.KeepAliveInterval = TimeSpan.FromSeconds(100); | ||
| clientOptions.KeepAliveTimeout = TimeSpan.FromSeconds(1); | ||
| }, | ||
| }; | ||
|
|
||
| return LoopbackWebSocketServer.RunAsync( | ||
| async (clientWebSocket, token) => | ||
| { | ||
| await VerifySendReceiveAsync(clientWebSocket, clientMsg, serverMsg, clientAckTcs, serverAckTcs.Task, token); | ||
|
|
||
| // We need to always have a read task active to keep processing pongs | ||
| var outstandingReadTask = clientWebSocket.ReceiveAsync(Array.Empty<byte>(), token); | ||
|
|
||
| await longDelayByServerTcs.Task.WaitAsync(token); | ||
|
|
||
| var result = await outstandingReadTask; | ||
| Assert.Equal(WebSocketMessageType.Binary, result.MessageType); | ||
| Assert.False(result.EndOfMessage); | ||
| Assert.Equal(0, result.Count); // we issued a zero byte read, just to wait for data to become available | ||
|
|
||
| Assert.Equal(WebSocketState.Open, clientWebSocket.State); | ||
|
|
||
| await VerifySendReceiveAsync(clientWebSocket, clientMsg, serverMsg, clientAckTcs, serverAckTcs.Task, token); | ||
|
|
||
| Assert.Equal(WebSocketState.Open, clientWebSocket.State); | ||
|
|
||
| await clientWebSocket.CloseAsync(WebSocketCloseStatus.NormalClosure, "", token); | ||
|
|
||
| Assert.Equal(WebSocketState.Closed, clientWebSocket.State); | ||
| }, | ||
| async (serverWebSocket, token) => | ||
| { | ||
| await VerifySendReceiveAsync(serverWebSocket, serverMsg, clientMsg, serverAckTcs, clientAckTcs.Task, token); | ||
|
|
||
| Assert.Equal(WebSocketState.Open, serverWebSocket.State); | ||
|
|
||
| await Task.Delay(LongDelay); | ||
|
|
||
| Assert.Equal(WebSocketState.Open, serverWebSocket.State); | ||
|
|
||
| // recreate already-completed TCS for another round | ||
| clientAckTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); | ||
| serverAckTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); | ||
|
|
||
| longDelayByServerTcs.SetResult(); | ||
|
|
||
| await VerifySendReceiveAsync(serverWebSocket, serverMsg, clientMsg, serverAckTcs, clientAckTcs.Task, token); | ||
|
|
||
| var closeFrame = await serverWebSocket.ReceiveAsync(Array.Empty<byte>(), token); | ||
| Assert.Equal(WebSocketMessageType.Close, closeFrame.MessageType); | ||
| Assert.Equal(WebSocketState.CloseReceived, serverWebSocket.State); | ||
|
|
||
| await serverWebSocket.CloseOutputAsync(WebSocketCloseStatus.NormalClosure, "", token); | ||
| Assert.Equal(WebSocketState.Closed, serverWebSocket.State); | ||
| }, | ||
| options, | ||
| timeoutCts.Token); | ||
| } | ||
|
|
||
| private static async Task VerifySendReceiveAsync(WebSocket ws, byte[] localMsg, byte[] remoteMsg, | ||
| TaskCompletionSource localAckTcs, Task remoteAck, CancellationToken cancellationToken) | ||
| { | ||
| var sendTask = ws.SendAsync(localMsg, WebSocketMessageType.Binary, endOfMessage: true, cancellationToken); | ||
|
|
||
| var recvBuf = new byte[remoteMsg.Length * 2]; | ||
| var recvResult = await ws.ReceiveAsync(recvBuf, cancellationToken).ConfigureAwait(false); | ||
|
|
||
| Assert.Equal(WebSocketMessageType.Binary, recvResult.MessageType); | ||
| Assert.Equal(remoteMsg.Length, recvResult.Count); | ||
| Assert.True(recvResult.EndOfMessage); | ||
| Assert.Equal(remoteMsg, recvBuf[..recvResult.Count]); | ||
|
|
||
| localAckTcs.SetResult(); | ||
|
|
||
| await sendTask.ConfigureAwait(false); | ||
| await remoteAck.WaitAsync(cancellationToken).ConfigureAwait(false); | ||
| } | ||
| } | ||
|
|
||
| // --- HTTP/1.1 WebSocket loopback tests --- | ||
|
|
||
| public class KeepAliveTest_Invoker_Loopback : KeepAliveTest_Loopback | ||
| { | ||
| public KeepAliveTest_Invoker_Loopback(ITestOutputHelper output) : base(output) { } | ||
| protected override bool UseCustomInvoker => true; | ||
| } | ||
|
|
||
| public class KeepAliveTest_HttpClient_Loopback : KeepAliveTest_Loopback | ||
| { | ||
| public KeepAliveTest_HttpClient_Loopback(ITestOutputHelper output) : base(output) { } | ||
| protected override bool UseHttpClient => true; | ||
| } | ||
|
|
||
| public class KeepAliveTest_SharedHandler_Loopback : KeepAliveTest_Loopback | ||
| { | ||
| public KeepAliveTest_SharedHandler_Loopback(ITestOutputHelper output) : base(output) { } | ||
| } | ||
|
|
||
| // --- HTTP/2 WebSocket loopback tests --- | ||
|
|
||
| public class KeepAliveTest_Invoker_Http2 : KeepAliveTest_Invoker_Loopback | ||
| { | ||
| public KeepAliveTest_Invoker_Http2(ITestOutputHelper output) : base(output) { } | ||
| protected override Version HttpVersion => Net.HttpVersion.Version20; | ||
| } | ||
|
|
||
| public class KeepAliveTest_HttpClient_Http2 : KeepAliveTest_HttpClient_Loopback | ||
| { | ||
| public KeepAliveTest_HttpClient_Http2(ITestOutputHelper output) : base(output) { } | ||
| protected override Version HttpVersion => Net.HttpVersion.Version20; | ||
| } | ||
| } |
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
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
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
Oops, something went wrong.
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.
Uh oh!
There was an error while loading. Please reload this page.