Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
e66ff59
Set the WebSocketException to Faulted to fix tests
kjpou1 Dec 9, 2020
1bd6fa6
Fix the exceptions that are being thrown to coincide with test expect…
kjpou1 Dec 9, 2020
5bd616a
Fix Dispose we are not processing it multiple times.
kjpou1 Dec 9, 2020
334a002
Fix Abort code so the messages align correctly with the tests.
kjpou1 Dec 9, 2020
4bc9bc7
Set the WebSocketException to Faulted to fix test expectations
kjpou1 Dec 9, 2020
c13cb26
Close the connections correctly.
kjpou1 Dec 9, 2020
84315f4
Merge branch 'master' of https://github.com/dotnet/runtime into wasm-…
kjpou1 Dec 10, 2020
00c6ef7
Fix Abort test Abort_CloseAndAbort_Success
kjpou1 Dec 10, 2020
2d13833
- Fixes for ReceiveAsyncTest
kjpou1 Dec 10, 2020
616c039
Merge branch 'master' of https://github.com/dotnet/runtime into wasm-…
kjpou1 Dec 11, 2020
2ce6b21
Merge branch 'master' of https://github.com/dotnet/runtime into wasm-…
kjpou1 Jan 5, 2021
b1c21c0
Remove ActiveIssue attributes that should be fixed
lewing Jan 5, 2021
390bbae
Merge branch 'master' of https://github.com/dotnet/runtime into wasm-…
kjpou1 Jan 6, 2021
4f21b1d
Merge branch 'master' of https://github.com/dotnet/runtime into wasm-…
kjpou1 Jan 11, 2021
fc15701
Add back code after merge conflict
kjpou1 Jan 11, 2021
e571896
Merge branch 'master' of https://github.com/dotnet/runtime into wasm-…
kjpou1 Jan 12, 2021
db403cd
Fix tests that were failing when expecting CloseSent as a valid state.
kjpou1 Jan 12, 2021
a321d6e
Fix the Abort and Cancel never ending tests.
kjpou1 Jan 12, 2021
41692d5
Merge branch 'master' of https://github.com/dotnet/runtime into wasm-…
kjpou1 Jan 13, 2021
8754b33
Add ActiveIssue to websocket client SendRecieve tests
kjpou1 Jan 13, 2021
e254ac0
Add ActiveIssue to websocket client SendRecieve tests
kjpou1 Jan 13, 2021
6586e25
Add extra time to timeout for a couple of tests that were intermitten…
kjpou1 Jan 13, 2021
77fe753
Merge branch 'master' of https://github.com/dotnet/runtime into wasm-…
kjpou1 Jan 14, 2021
11fdc7d
Add ActiveIssue to websocket client SendRecieve tests
kjpou1 Jan 14, 2021
03b67b1
Merge branch 'master' of https://github.com/dotnet/runtime into wasm-…
kjpou1 Jan 18, 2021
90e7b79
Remove `Interlocked` code as per review comment.
kjpou1 Jan 18, 2021
188bdc1
Fix comment
kjpou1 Jan 18, 2021
15906e6
Merge branch 'master' of https://github.com/dotnet/runtime into wasm-…
kjpou1 Jan 25, 2021
0118322
Fix Abort tests on non chrome browsers.
kjpou1 Jan 25, 2021
be55fa0
Merge branch 'master' of https://github.com/dotnet/runtime into wasm-…
kjpou1 Jan 26, 2021
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ internal sealed class BrowserWebSocket : WebSocket
});

private TaskCompletionSource? _tcsClose;
private TaskCompletionSource? _tcsConnect;
private WebSocketCloseStatus? _innerWebSocketCloseStatus;
private string? _innerWebSocketCloseStatusDescription;

Expand All @@ -47,6 +48,7 @@ internal sealed class BrowserWebSocket : WebSocket
private MemoryStream? _writeBuffer;
private ReceivePayload? _bufferedPayload;
private readonly CancellationTokenSource _cts;
private int _closeStatus; // variable to track the close status after a close is sent.

// Stages of this class.
private int _state;
Expand All @@ -56,9 +58,12 @@ private enum InternalState
Created = 0,
Connecting = 1,
Connected = 2,
Disposed = 3
CloseSent = 3,
Disposed = 4,
Aborted = 5,
}

private bool _disposed;

/// <summary>
/// Initializes a new instance of the <see cref="System.Net.WebSockets.BrowserWebSocket"/> class.
Expand All @@ -78,14 +83,17 @@ public override WebSocketState State
{
get
{
if (_innerWebSocket != null && !_innerWebSocket.IsDisposed)
if (_innerWebSocket != null && !_innerWebSocket.IsDisposed && _state != (int)InternalState.Aborted)
{
return ReadyStateToDotNetState((int)_innerWebSocket.GetObjectProperty("readyState"));
}
return (InternalState)_state switch
{
InternalState.Created => WebSocketState.None,
InternalState.Connecting => WebSocketState.Connecting,
InternalState.Aborted => WebSocketState.Aborted,
InternalState.Disposed => WebSocketState.Closed,
InternalState.CloseSent => WebSocketState.CloseSent,
_ => WebSocketState.Closed
};
}
Expand All @@ -112,19 +120,24 @@ private static WebSocketState ReadyStateToDotNetState(int readyState) =>

internal async Task ConnectAsyncJavaScript(Uri uri, CancellationToken cancellationToken, List<string>? requestedSubProtocols)
{
// Check that we have not started already
int priorState = Interlocked.CompareExchange(ref _state, (int)InternalState.Connecting, (int)InternalState.Created);
if (priorState == (int)InternalState.Disposed)
{
throw new ObjectDisposedException(GetType().FullName);
}
else if (priorState != (int)InternalState.Created)
// Check that we have not started already.
int prevState = _state;
_state = _state == (int)InternalState.Created ? (int)InternalState.Connecting : _state;

switch ((InternalState)prevState)
{
throw new InvalidOperationException(SR.net_WebSockets_AlreadyStarted);
case InternalState.Disposed:
throw new ObjectDisposedException(GetType().FullName);

case InternalState.Created:
break;

default:
throw new InvalidOperationException(SR.net_WebSockets_AlreadyStarted);
}

CancellationTokenRegistration connectRegistration = cancellationToken.Register(cts => ((CancellationTokenSource)cts!).Cancel(), _cts);
TaskCompletionSource tcsConnect = new TaskCompletionSource();
_tcsConnect = new TaskCompletionSource();

// For Abort/Dispose. Calling Abort on the request at any point will close the connection.
_cts.Token.Register(s => ((BrowserWebSocket)s!).AbortRequest(), this);
Expand Down Expand Up @@ -163,20 +176,21 @@ internal async Task ConnectAsyncJavaScript(Uri uri, CancellationToken cancellati
_innerWebSocketCloseStatusDescription = closeEvt.GetObjectProperty("reason")?.ToString();
_receiveMessageQueue.Writer.TryWrite(new ReceivePayload(Array.Empty<byte>(), WebSocketMessageType.Close));
NativeCleanup();
if ((InternalState)_state == InternalState.Connecting)
if ((InternalState)_state == InternalState.Connecting || (InternalState)_state == InternalState.Aborted)
{
_state = (int)InternalState.Disposed;
if (cancellationToken.IsCancellationRequested)
{
tcsConnect.TrySetCanceled(cancellationToken);
_tcsConnect.TrySetCanceled(cancellationToken);
}
else
{
tcsConnect.TrySetException(new WebSocketException(WebSocketError.NativeError));
_tcsConnect.TrySetException(new WebSocketException(WebSocketError.NativeError));
}
}
else
{
_tcsClose?.SetResult();
_tcsClose?.TrySetResult();
}
}
};
Expand All @@ -192,19 +206,21 @@ internal async Task ConnectAsyncJavaScript(Uri uri, CancellationToken cancellati
if (!cancellationToken.IsCancellationRequested)
{
// Change internal _state to 'Connected' to enable the other methods
if (Interlocked.CompareExchange(ref _state, (int)InternalState.Connected, (int)InternalState.Connecting) != (int)InternalState.Connecting)
int prevState = _state;
_state = _state == (int)InternalState.Connecting ? (int)InternalState.Connected : _state;
if (prevState != (int)InternalState.Connecting)
{
// Aborted/Disposed during connect.
tcsConnect.TrySetException(new ObjectDisposedException(GetType().FullName));
_tcsConnect.TrySetException(new ObjectDisposedException(GetType().FullName));
}
else
{
tcsConnect.SetResult();
_tcsConnect.SetResult();
}
}
else
{
tcsConnect.SetCanceled(cancellationToken);
_tcsConnect.SetCanceled(cancellationToken);
}
}
};
Expand All @@ -217,7 +233,7 @@ internal async Task ConnectAsyncJavaScript(Uri uri, CancellationToken cancellati

// Attach the onMessage callaback
_innerWebSocket.SetObjectProperty("onmessage", _onMessage);
await tcsConnect.Task.ConfigureAwait(continueOnCapturedContext: true);
await _tcsConnect.Task.ConfigureAwait(continueOnCapturedContext: true);
}
catch (Exception wse)
{
Expand All @@ -227,7 +243,7 @@ internal async Task ConnectAsyncJavaScript(Uri uri, CancellationToken cancellati
case OperationCanceledException:
throw;
default:
throw new WebSocketException(SR.net_webstatus_ConnectFailure, wse);
throw new WebSocketException(WebSocketError.Faulted, SR.net_webstatus_ConnectFailure, wse);
}
}
finally
Expand Down Expand Up @@ -318,32 +334,51 @@ private void NativeCleanup()

public override void Dispose()
{
int priorState = Interlocked.Exchange(ref _state, (int)InternalState.Disposed);
if (priorState == (int)InternalState.Disposed)
if (!_disposed)
{
// No cleanup required.
return;
}
if (_state < (int)InternalState.Aborted) {
_state = (int)InternalState.Disposed;
}
_disposed = true;

// registered by the CancellationTokenSource cts in the connect method
_cts.Cancel(false);
_cts.Dispose();
if (!_cts.IsCancellationRequested)
{
// registered by the CancellationTokenSource cts in the connect method
_cts.Cancel(false);
_cts.Dispose();
}

_writeBuffer?.Dispose();
_receiveMessageQueue.Writer.Complete();
_writeBuffer?.Dispose();
_receiveMessageQueue.Writer.TryComplete();

NativeCleanup();
NativeCleanup();

_innerWebSocket?.Dispose();
_innerWebSocket?.Dispose();
}
}

// This method is registered by the CancellationTokenSource cts in the connect method
// and called by Dispose or Abort so that any open websocket connection can be closed.
private async void AbortRequest()
{
if (State == WebSocketState.Open || State == WebSocketState.Connecting)
switch (State)
{
await CloseAsyncCore(WebSocketCloseStatus.NormalClosure, SR.net_WebSockets_Connection_Aborted, CancellationToken.None).ConfigureAwait(continueOnCapturedContext: true);
case WebSocketState.Open:
case WebSocketState.Connecting:
{
await CloseAsyncCore(WebSocketCloseStatus.NormalClosure, SR.net_WebSockets_Connection_Aborted, CancellationToken.None).ConfigureAwait(continueOnCapturedContext: true);
// The following code is for those browsers that do not set Close and send an onClose event in certain instances i.e. firefox and safari.
// chrome will send an onClose event and we tear down the websocket there.
if (ReadyStateToDotNetState(_closeStatus) == WebSocketState.CloseSent)
{
_writeBuffer?.Dispose();
_receiveMessageQueue.Writer.TryWrite(new ReceivePayload(Array.Empty<byte>(), WebSocketMessageType.Close));
_receiveMessageQueue.Writer.TryComplete();
NativeCleanup();
_tcsConnect?.TrySetCanceled();
}
}
break;
}
}

Expand Down Expand Up @@ -423,6 +458,20 @@ public override Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType m
return Task.CompletedTask;
}

// This method is registered by the CancellationTokenSource in the receive async method
private async void CancelRequest()
{
int prevState = _state;
_state = (int)InternalState.Aborted;
_receiveMessageQueue.Writer.TryComplete();
if (prevState == (int)InternalState.Connected || prevState == (int)InternalState.Connecting)
{
if (prevState == (int)InternalState.Connecting)
_state = (int)InternalState.CloseSent;
await CloseAsyncCore(WebSocketCloseStatus.NormalClosure, SR.net_WebSockets_Connection_Aborted, CancellationToken.None).ConfigureAwait(continueOnCapturedContext: true);
}
}

/// <summary>
/// Receives data on <see cref="System.Net.WebSockets.ClientWebSocket"/> as an asynchronous operation.
/// </summary>
Expand All @@ -431,22 +480,43 @@ public override Task SendAsync(ArraySegment<byte> buffer, WebSocketMessageType m
/// <param name="cancellationToken">Cancellation token.</param>
public override async Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byte> buffer, CancellationToken cancellationToken)
{
WebSocketValidate.ValidateArraySegment(buffer, nameof(buffer));

ThrowIfDisposed();
ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseSent);
_bufferedPayload ??= await _receiveMessageQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: true);
if (cancellationToken.IsCancellationRequested)
{
return await Task.FromException<WebSocketReceiveResult>(new OperationCanceledException()).ConfigureAwait(continueOnCapturedContext: true);
}

CancellationTokenSource _receiveCTS = new CancellationTokenSource();
CancellationTokenRegistration receiveRegistration = cancellationToken.Register(cts => ((CancellationTokenSource)cts!).Cancel(), _receiveCTS);
_receiveCTS.Token.Register(s => ((BrowserWebSocket)s!).CancelRequest(), this);

try
{
bool endOfMessage = _bufferedPayload.BufferPayload(buffer, out WebSocketReceiveResult receiveResult);
WebSocketValidate.ValidateArraySegment(buffer, nameof(buffer));

ThrowIfDisposed();
ThrowOnInvalidState(State, WebSocketState.Open, WebSocketState.CloseSent);
_bufferedPayload ??= await _receiveMessageQueue.Reader.ReadAsync(cancellationToken).ConfigureAwait(continueOnCapturedContext: true);
bool endOfMessage = _bufferedPayload!.BufferPayload(buffer, out WebSocketReceiveResult receiveResult);
if (endOfMessage)
_bufferedPayload = null;
return receiveResult;
}
catch (Exception exc)
{
throw new WebSocketException(WebSocketError.NativeError, exc);
switch (exc)
{
case OperationCanceledException:
return await Task.FromException<WebSocketReceiveResult>(exc).ConfigureAwait(continueOnCapturedContext: true);
case ChannelClosedException:
return await Task.FromException<WebSocketReceiveResult>(new WebSocketException(WebSocketError.InvalidState, SR.Format(SR.net_WebSockets_InvalidState, State, "Open, CloseSent"))).ConfigureAwait(continueOnCapturedContext: true);
default:
return await Task.FromException<WebSocketReceiveResult>(new WebSocketException(WebSocketError.InvalidState, SR.Format(SR.net_WebSockets_InvalidState, State, "Open, CloseSent"))).ConfigureAwait(continueOnCapturedContext: true);
}
}
finally
{
receiveRegistration.Unregister();
}
}

Expand All @@ -455,12 +525,20 @@ public override async Task<WebSocketReceiveResult> ReceiveAsync(ArraySegment<byt
/// </summary>
public override void Abort()
{
if (_state == (int)InternalState.Disposed)
if (_state != (int)InternalState.Disposed)
{
return;
int prevState = _state;
if (prevState != (int)InternalState.Connecting)
{
_state = (int)InternalState.Aborted;
}

if (prevState < (int)InternalState.Aborted)
{
_cts.Cancel(true);
_tcsClose?.TrySetResult();
}
}
_state = (int)WebSocketState.Aborted;
Dispose();
}

public override Task CloseAsync(WebSocketCloseStatus closeStatus, string? statusDescription, CancellationToken cancellationToken)
Expand All @@ -478,7 +556,6 @@ public override Task CloseAsync(WebSocketCloseStatus closeStatus, string? status
{
return Task.FromException(exc);
}

return CloseAsyncCore(closeStatus, statusDescription, cancellationToken);
}

Expand All @@ -490,6 +567,7 @@ private Task CloseAsyncCore(WebSocketCloseStatus closeStatus, string? statusDesc
_innerWebSocketCloseStatus = closeStatus;
_innerWebSocketCloseStatusDescription = statusDescription;
_innerWebSocket!.Invoke("close", (int)closeStatus, statusDescription);
_closeStatus = (int)_innerWebSocket.GetObjectProperty("readyState");
return _tcsClose.Task;
}
catch (Exception exc)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ public void Dispose()

public void Abort()
{
_abortSource.Cancel();
WebSocket?.Abort();
}

Expand Down Expand Up @@ -67,7 +68,7 @@ public async Task ConnectAsync(Uri uri, CancellationToken cancellationToken, Cli
case OperationCanceledException _ when cancellationToken.IsCancellationRequested:
throw;
default:
throw new WebSocketException(SR.net_webstatus_ConnectFailure, exc);
throw new WebSocketException(WebSocketError.Faulted, SR.net_webstatus_ConnectFailure, exc);
}
}
}
Expand Down
4 changes: 0 additions & 4 deletions src/libraries/System.Net.WebSockets.Client/tests/AbortTest.cs
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,6 @@ public AbortTest(ITestOutputHelper output) : base(output) { }

[OuterLoop]
[ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/45674", TestPlatforms.Browser)]
public async Task Abort_ConnectAndAbort_ThrowsWebSocketExceptionWithmessage(Uri server)
{
using (var cws = new ClientWebSocket())
Expand All @@ -43,7 +42,6 @@ public async Task Abort_ConnectAndAbort_ThrowsWebSocketExceptionWithmessage(Uri

[OuterLoop]
[ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/45674", TestPlatforms.Browser)]
public async Task Abort_SendAndAbort_Success(Uri server)
{
await TestCancellation(async (cws) =>
Expand All @@ -64,7 +62,6 @@ await TestCancellation(async (cws) =>

[OuterLoop]
[ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/45674", TestPlatforms.Browser)]
public async Task Abort_ReceiveAndAbort_Success(Uri server)
{
await TestCancellation(async (cws) =>
Expand All @@ -89,7 +86,6 @@ await cws.SendAsync(

[OuterLoop]
[ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/45674", TestPlatforms.Browser)]
public async Task Abort_CloseAndAbort_Success(Uri server)
{
await TestCancellation(async (cws) =>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -131,7 +131,6 @@ public async Task ReceiveAsync_CancelThenReceive_ThrowsOperationCanceledExceptio

[OuterLoop("Uses external servers")]
[ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/45674", TestPlatforms.Browser)]
public async Task ReceiveAsync_ReceiveThenCancel_ThrowsOperationCanceledException(Uri server)
{
using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output))
Expand All @@ -148,7 +147,6 @@ public async Task ReceiveAsync_ReceiveThenCancel_ThrowsOperationCanceledExceptio

[OuterLoop("Uses external servers")]
[ConditionalTheory(nameof(WebSocketsSupported)), MemberData(nameof(EchoServers))]
[ActiveIssue("https://github.com/dotnet/runtime/issues/45674", TestPlatforms.Browser)]
public async Task ReceiveAsync_AfterCancellationDoReceiveAsync_ThrowsWebSocketException(Uri server)
{
using (ClientWebSocket cws = await WebSocketHelper.GetConnectedWebSocket(server, TimeOutMilliseconds, _output))
Expand Down
Loading