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
This commit is contained in:
parent
ab451b53b7
commit
c53514fa19
|
|
@ -22,7 +22,7 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
{
|
{
|
||||||
public class HubConnectionContext
|
public class HubConnectionContext
|
||||||
{
|
{
|
||||||
private static readonly Action<object> _abortedCallback = AbortConnection;
|
private static readonly WaitCallback _abortedCallback = AbortConnection;
|
||||||
|
|
||||||
private readonly ConnectionContext _connectionContext;
|
private readonly ConnectionContext _connectionContext;
|
||||||
private readonly ILogger _logger;
|
private readonly ILogger _logger;
|
||||||
|
|
@ -271,7 +271,7 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
Input.CancelPendingRead();
|
Input.CancelPendingRead();
|
||||||
|
|
||||||
// We fire and forget since this can trigger user code to run
|
// We fire and forget since this can trigger user code to run
|
||||||
Task.Factory.StartNew(_abortedCallback, this);
|
ThreadPool.QueueUserWorkItem(_abortedCallback, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
internal async Task<bool> HandshakeAsync(TimeSpan timeout, IReadOnlyList<string> supportedProtocols, IHubProtocolResolver protocolResolver,
|
internal async Task<bool> HandshakeAsync(TimeSpan timeout, IReadOnlyList<string> supportedProtocols, IHubProtocolResolver protocolResolver,
|
||||||
|
|
@ -425,15 +425,15 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
connection._connectionAbortedTokenSource.Cancel();
|
connection._connectionAbortedTokenSource.Cancel();
|
||||||
|
|
||||||
// Communicate the fact that we're finished triggering abort callbacks
|
|
||||||
connection._abortCompletedTcs.TrySetResult(null);
|
|
||||||
}
|
}
|
||||||
catch (Exception ex)
|
catch (Exception ex)
|
||||||
{
|
{
|
||||||
// TODO: Should we log if the cancellation callback fails? This is more preventative to make sure
|
Log.AbortFailed(connection._logger, ex);
|
||||||
// we don't end up with an unobserved task
|
}
|
||||||
connection._abortCompletedTcs.TrySetException(ex);
|
finally
|
||||||
|
{
|
||||||
|
// Communicate the fact that we're finished triggering abort callbacks
|
||||||
|
connection._abortCompletedTcs.TrySetResult(null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -461,6 +461,9 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
private static readonly Action<ILogger, string, int, Exception> _protocolVersionFailed =
|
private static readonly Action<ILogger, string, int, Exception> _protocolVersionFailed =
|
||||||
LoggerMessage.Define<string, int>(LogLevel.Warning, new EventId(7, "ProtocolVersionFailed"), "Server does not support version {Version} of the {Protocol} protocol.");
|
LoggerMessage.Define<string, int>(LogLevel.Warning, new EventId(7, "ProtocolVersionFailed"), "Server does not support version {Version} of the {Protocol} protocol.");
|
||||||
|
|
||||||
|
private static readonly Action<ILogger, Exception> _abortFailed =
|
||||||
|
LoggerMessage.Define(LogLevel.Trace, new EventId(8, "AbortFailed"), "Abort callback failed.");
|
||||||
|
|
||||||
public static void HandshakeComplete(ILogger logger, string hubProtocol)
|
public static void HandshakeComplete(ILogger logger, string hubProtocol)
|
||||||
{
|
{
|
||||||
_handshakeComplete(logger, hubProtocol, null);
|
_handshakeComplete(logger, hubProtocol, null);
|
||||||
|
|
@ -495,7 +498,11 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
{
|
{
|
||||||
_protocolVersionFailed(logger, protocolName, version, null);
|
_protocolVersionFailed(logger, protocolName, version, null);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
|
public static void AbortFailed(ILogger logger, Exception exception)
|
||||||
|
{
|
||||||
|
_abortFailed(logger, exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -126,15 +126,8 @@ namespace Microsoft.AspNetCore.SignalR
|
||||||
// We wait on abort to complete, this is so that we can guarantee that all callbacks have fired
|
// We wait on abort to complete, this is so that we can guarantee that all callbacks have fired
|
||||||
// before OnDisconnectedAsync
|
// before OnDisconnectedAsync
|
||||||
|
|
||||||
try
|
// Ensure the connection is aborted before firing disconnect
|
||||||
{
|
await connection.AbortAsync();
|
||||||
// Ensure the connection is aborted before firing disconnect
|
|
||||||
await connection.AbortAsync();
|
|
||||||
}
|
|
||||||
catch (Exception ex)
|
|
||||||
{
|
|
||||||
Log.AbortFailed(_logger, ex);
|
|
||||||
}
|
|
||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
|
|
|
||||||
|
|
@ -549,6 +549,28 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
void Send(string message);
|
void Send(string message);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public class ErrorInAbortedTokenHub : Hub
|
||||||
|
{
|
||||||
|
public override Task OnConnectedAsync()
|
||||||
|
{
|
||||||
|
Context.Items[nameof(OnConnectedAsync)] = true;
|
||||||
|
|
||||||
|
Context.ConnectionAborted.Register(() =>
|
||||||
|
{
|
||||||
|
throw new InvalidOperationException("BOOM");
|
||||||
|
});
|
||||||
|
|
||||||
|
return base.OnConnectedAsync();
|
||||||
|
}
|
||||||
|
|
||||||
|
public override Task OnDisconnectedAsync(Exception exception)
|
||||||
|
{
|
||||||
|
Context.Items[nameof(OnDisconnectedAsync)] = true;
|
||||||
|
|
||||||
|
return base.OnDisconnectedAsync(exception);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public class ConnectionLifetimeHub : Hub
|
public class ConnectionLifetimeHub : Hub
|
||||||
{
|
{
|
||||||
private readonly ConnectionLifetimeState _state;
|
private readonly ConnectionLifetimeState _state;
|
||||||
|
|
|
||||||
|
|
@ -69,6 +69,29 @@ namespace Microsoft.AspNetCore.SignalR.Tests
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
[Fact]
|
||||||
|
public async Task OnDisconnectedAsyncTriggersWhenAbortedTokenCallbackThrows()
|
||||||
|
{
|
||||||
|
var serviceProvider = HubConnectionHandlerTestUtils.CreateServiceProvider();
|
||||||
|
var connectionHandler = serviceProvider.GetService<HubConnectionHandler<ErrorInAbortedTokenHub>>();
|
||||||
|
|
||||||
|
using (var client = new TestClient())
|
||||||
|
{
|
||||||
|
var connectionHandlerTask = await client.ConnectAsync(connectionHandler);
|
||||||
|
|
||||||
|
// kill the connection
|
||||||
|
client.Dispose();
|
||||||
|
|
||||||
|
await connectionHandlerTask.OrTimeout();
|
||||||
|
|
||||||
|
var firedOnConnected = (bool)client.Connection.Items[nameof(ErrorInAbortedTokenHub.OnConnectedAsync)];
|
||||||
|
var firedOnDisconnected = (bool)client.Connection.Items[nameof(ErrorInAbortedTokenHub.OnDisconnectedAsync)];
|
||||||
|
|
||||||
|
Assert.True(firedOnConnected);
|
||||||
|
Assert.True(firedOnDisconnected);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
[Fact]
|
[Fact]
|
||||||
public async Task AbortFromHubMethodForcesClientDisconnect()
|
public async Task AbortFromHubMethodForcesClientDisconnect()
|
||||||
{
|
{
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue