Skip to content

Commit c53514f

Browse files
authored
Don't throw from AbortAsync (#2166)
- Log from inside of HubConnectionContext if the user callback failed. - Use ThreadPool.QUWI instead of Task.Factory.StartNew. - Remove try catch from HubConnectionHandler
1 parent ab451b5 commit c53514f

File tree

4 files changed

+63
-18
lines changed

4 files changed

+63
-18
lines changed

src/Microsoft.AspNetCore.SignalR.Core/HubConnectionContext.cs

Lines changed: 16 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.SignalR
2222
{
2323
public class HubConnectionContext
2424
{
25-
private static readonly Action<object> _abortedCallback = AbortConnection;
25+
private static readonly WaitCallback _abortedCallback = AbortConnection;
2626

2727
private readonly ConnectionContext _connectionContext;
2828
private readonly ILogger _logger;
@@ -271,7 +271,7 @@ public virtual void Abort()
271271
Input.CancelPendingRead();
272272

273273
// We fire and forget since this can trigger user code to run
274-
Task.Factory.StartNew(_abortedCallback, this);
274+
ThreadPool.QueueUserWorkItem(_abortedCallback, this);
275275
}
276276

277277
internal async Task<bool> HandshakeAsync(TimeSpan timeout, IReadOnlyList<string> supportedProtocols, IHubProtocolResolver protocolResolver,
@@ -425,15 +425,15 @@ private static void AbortConnection(object state)
425425
try
426426
{
427427
connection._connectionAbortedTokenSource.Cancel();
428-
429-
// Communicate the fact that we're finished triggering abort callbacks
430-
connection._abortCompletedTcs.TrySetResult(null);
431428
}
432429
catch (Exception ex)
433430
{
434-
// TODO: Should we log if the cancellation callback fails? This is more preventative to make sure
435-
// we don't end up with an unobserved task
436-
connection._abortCompletedTcs.TrySetException(ex);
431+
Log.AbortFailed(connection._logger, ex);
432+
}
433+
finally
434+
{
435+
// Communicate the fact that we're finished triggering abort callbacks
436+
connection._abortCompletedTcs.TrySetResult(null);
437437
}
438438
}
439439

@@ -461,6 +461,9 @@ private static class Log
461461
private static readonly Action<ILogger, string, int, Exception> _protocolVersionFailed =
462462
LoggerMessage.Define<string, int>(LogLevel.Warning, new EventId(7, "ProtocolVersionFailed"), "Server does not support version {Version} of the {Protocol} protocol.");
463463

464+
private static readonly Action<ILogger, Exception> _abortFailed =
465+
LoggerMessage.Define(LogLevel.Trace, new EventId(8, "AbortFailed"), "Abort callback failed.");
466+
464467
public static void HandshakeComplete(ILogger logger, string hubProtocol)
465468
{
466469
_handshakeComplete(logger, hubProtocol, null);
@@ -495,7 +498,11 @@ public static void ProtocolVersionFailed(ILogger logger, string protocolName, in
495498
{
496499
_protocolVersionFailed(logger, protocolName, version, null);
497500
}
498-
}
499501

502+
public static void AbortFailed(ILogger logger, Exception exception)
503+
{
504+
_abortFailed(logger, exception);
505+
}
506+
}
500507
}
501508
}

src/Microsoft.AspNetCore.SignalR.Core/HubConnectionHandler.cs

Lines changed: 2 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -126,15 +126,8 @@ private async Task HubOnDisconnectedAsync(HubConnectionContext connection, Excep
126126
// We wait on abort to complete, this is so that we can guarantee that all callbacks have fired
127127
// before OnDisconnectedAsync
128128

129-
try
130-
{
131-
// Ensure the connection is aborted before firing disconnect
132-
await connection.AbortAsync();
133-
}
134-
catch (Exception ex)
135-
{
136-
Log.AbortFailed(_logger, ex);
137-
}
129+
// Ensure the connection is aborted before firing disconnect
130+
await connection.AbortAsync();
138131

139132
try
140133
{

test/Microsoft.AspNetCore.SignalR.Tests/HubConnectionHandlerTestUtils/Hubs.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -549,6 +549,28 @@ public interface IVoidReturningTypedHubClient
549549
void Send(string message);
550550
}
551551

552+
public class ErrorInAbortedTokenHub : Hub
553+
{
554+
public override Task OnConnectedAsync()
555+
{
556+
Context.Items[nameof(OnConnectedAsync)] = true;
557+
558+
Context.ConnectionAborted.Register(() =>
559+
{
560+
throw new InvalidOperationException("BOOM");
561+
});
562+
563+
return base.OnConnectedAsync();
564+
}
565+
566+
public override Task OnDisconnectedAsync(Exception exception)
567+
{
568+
Context.Items[nameof(OnDisconnectedAsync)] = true;
569+
570+
return base.OnDisconnectedAsync(exception);
571+
}
572+
}
573+
552574
public class ConnectionLifetimeHub : Hub
553575
{
554576
private readonly ConnectionLifetimeState _state;

test/Microsoft.AspNetCore.SignalR.Tests/HubConnectionHandlerTests.cs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -69,6 +69,29 @@ public async Task ConnectionAbortedTokenTriggers()
6969
}
7070
}
7171

72+
[Fact]
73+
public async Task OnDisconnectedAsyncTriggersWhenAbortedTokenCallbackThrows()
74+
{
75+
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider();
76+
var connectionHandler = serviceProvider.GetService<HubConnectionHandler<ErrorInAbortedTokenHub>>();
77+
78+
using (var client = new TestClient())
79+
{
80+
var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
81+
82+
// kill the connection
83+
client.Dispose();
84+
85+
await connectionHandlerTask.OrTimeout();
86+
87+
var firedOnConnected = (bool)client.Connection.Items[nameof(ErrorInAbortedTokenHub.OnConnectedAsync)];
88+
var firedOnDisconnected = (bool)client.Connection.Items[nameof(ErrorInAbortedTokenHub.OnDisconnectedAsync)];
89+
90+
Assert.True(firedOnConnected);
91+
Assert.True(firedOnDisconnected);
92+
}
93+
}
94+
7295
[Fact]
7396
public async Task AbortFromHubMethodForcesClientDisconnect()
7497
{

0 commit comments

Comments
 (0)