diff --git a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs index 216a39b1c2..17e41b4db9 100644 --- a/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs +++ b/src/SignalR/clients/csharp/Client/test/FunctionalTests/HubConnectionTests.cs @@ -1349,7 +1349,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests }; var protocol = HubProtocols[protocolName]; - await using (var server = await StartServer(write => write.EventId.Name == "FailedWritingMessage")) + await using (var server = await StartServer(write => + { + return write.EventId.Name == "FailedWritingMessage" || write.EventId.Name == "ReceivedCloseWithError" + || write.EventId.Name == "ShutdownWithError"; + })) { var connection = CreateHubConnection(server.Url, "/default", HttpTransportType.WebSockets, protocol, LoggerFactory); var closedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -1361,9 +1365,12 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests var result = connection.InvokeAsync(nameof(TestHub.CallWithUnserializableObject)); // The connection should close. - Assert.Null(await closedTcs.Task.OrTimeout()); + var exception = await closedTcs.Task.OrTimeout(); + Assert.Contains("Connection closed with an error.", exception.Message); - await Assert.ThrowsAsync(() => result).OrTimeout(); + var hubException = await Assert.ThrowsAsync(() => result).OrTimeout(); + Assert.Contains("Connection closed with an error.", hubException.Message); + Assert.Contains(exceptionSubstring, hubException.Message); } catch (Exception ex) { @@ -1396,7 +1403,11 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests }; var protocol = HubProtocols[protocolName]; - await using (var server = await StartServer(write => write.EventId.Name == "FailedWritingMessage")) + await using (var server = await StartServer(write => + { + return write.EventId.Name == "FailedWritingMessage" || write.EventId.Name == "ReceivedCloseWithError" + || write.EventId.Name == "ShutdownWithError"; + })) { var connection = CreateHubConnection(server.Url, "/default", HttpTransportType.LongPolling, protocol, LoggerFactory); var closedTcs = new TaskCompletionSource(TaskCreationOptions.RunContinuationsAsynchronously); @@ -1408,9 +1419,12 @@ namespace Microsoft.AspNetCore.SignalR.Client.FunctionalTests var result = connection.InvokeAsync(nameof(TestHub.GetUnserializableObject)).OrTimeout(); // The connection should close. - Assert.Null(await closedTcs.Task.OrTimeout()); + var exception = await closedTcs.Task.OrTimeout(); + Assert.Contains("Connection closed with an error.", exception.Message); - await Assert.ThrowsAsync(() => result).OrTimeout(); + var hubException = await Assert.ThrowsAsync(() => result).OrTimeout(); + Assert.Contains("Connection closed with an error.", hubException.Message); + Assert.Contains(exceptionSubstring, hubException.Message); } catch (Exception ex) { diff --git a/src/SignalR/server/Core/src/HubConnectionContext.cs b/src/SignalR/server/Core/src/HubConnectionContext.cs index 88ebde464c..08913ae139 100644 --- a/src/SignalR/server/Core/src/HubConnectionContext.cs +++ b/src/SignalR/server/Core/src/HubConnectionContext.cs @@ -149,14 +149,19 @@ namespace Microsoft.AspNetCore.SignalR [SuppressMessage("ApiDesign", "RS0026:Do not add multiple overloads with optional parameters", Justification = "Required to maintain compatibility")] public virtual ValueTask WriteAsync(HubMessage message, CancellationToken cancellationToken = default) + { + return WriteAsync(message, ignoreAbort: false, cancellationToken); + } + + internal ValueTask WriteAsync(HubMessage message, bool ignoreAbort, CancellationToken cancellationToken = default) { // Try to grab the lock synchronously, if we fail, go to the slower path if (!_writeLock.Wait(0)) { - return new ValueTask(WriteSlowAsync(message, cancellationToken)); + return new ValueTask(WriteSlowAsync(message, ignoreAbort, cancellationToken)); } - if (_connectionAborted) + if (_connectionAborted && !ignoreAbort) { _writeLock.Release(); return default; @@ -275,14 +280,14 @@ namespace Microsoft.AspNetCore.SignalR } } - private async Task WriteSlowAsync(HubMessage message, CancellationToken cancellationToken) + private async Task WriteSlowAsync(HubMessage message, bool ignoreAbort, CancellationToken cancellationToken) { // Failed to get the lock immediately when entering WriteAsync so await until it is available await _writeLock.WaitAsync(cancellationToken); try { - if (_connectionAborted) + if (_connectionAborted && !ignoreAbort) { return; } @@ -304,7 +309,7 @@ namespace Microsoft.AspNetCore.SignalR private async Task WriteSlowAsync(SerializedHubMessage message, CancellationToken cancellationToken) { // Failed to get the lock immediately when entering WriteAsync so await until it is available - await _writeLock.WaitAsync(); + await _writeLock.WaitAsync(cancellationToken); try { diff --git a/src/SignalR/server/Core/src/HubConnectionHandler.cs b/src/SignalR/server/Core/src/HubConnectionHandler.cs index d2d77e0fe6..a16f42f490 100644 --- a/src/SignalR/server/Core/src/HubConnectionHandler.cs +++ b/src/SignalR/server/Core/src/HubConnectionHandler.cs @@ -226,7 +226,7 @@ namespace Microsoft.AspNetCore.SignalR try { - await connection.WriteAsync(closeMessage); + await connection.WriteAsync(closeMessage, ignoreAbort: true); } catch (Exception ex) { diff --git a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs index 862cde22d4..609a2550ae 100644 --- a/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs +++ b/src/SignalR/server/SignalR/test/HubConnectionHandlerTests.cs @@ -155,6 +155,9 @@ namespace Microsoft.AspNetCore.SignalR.Tests await client.SendInvocationAsync(nameof(AbortHub.Kill)).OrTimeout(); + var close = Assert.IsType(await client.ReadAsync().OrTimeout()); + Assert.False(close.AllowReconnect); + await connectionHandlerTask.OrTimeout(); Assert.Null(client.TryRead()); @@ -955,15 +958,18 @@ namespace Microsoft.AspNetCore.SignalR.Tests { var connectionHandlerTask = await client.ConnectAsync(connectionHandler); - var invokeTask = client.InvokeAsync(nameof(MethodHub.BlockingMethod)); + await client.SendInvocationAsync(nameof(MethodHub.BlockingMethod)).OrTimeout(); client.Connection.Abort(); + var closeMessage = Assert.IsType(await client.ReadAsync().OrTimeout()); + Assert.False(closeMessage.AllowReconnect); + // If this completes then the server has completed the connection await connectionHandlerTask.OrTimeout(); // Nothing written to connection because it was closed - Assert.False(invokeTask.IsCompleted); + Assert.Null(client.TryRead()); } } } @@ -1019,16 +1025,11 @@ namespace Microsoft.AspNetCore.SignalR.Tests // kill the connection client.Dispose(); + var message = Assert.IsType(client.TryRead()); + Assert.True(message.AllowReconnect); + // Ensure the client channel is empty - var message = client.TryRead(); - switch (message) - { - case CloseMessage close: - break; - default: - Assert.Null(message); - break; - } + Assert.Null(client.TryRead()); await connectionHandlerTask.OrTimeout(); }