[SignalR] Avoid deadlock with closing and user callbacks (#19612)

This commit is contained in:
Brennan 2020-03-06 15:25:51 -08:00 committed by GitHub
parent 7fabb6c9f4
commit f053620895
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
2 changed files with 57 additions and 2 deletions

View File

@ -1214,11 +1214,13 @@ namespace Microsoft.AspNetCore.SignalR.Client
finally
{
invocationMessageChannel.Writer.TryComplete();
await invocationMessageReceiveTask;
timer.Stop();
await timerTask;
uploadStreamSource.Cancel();
await HandleConnectionClose(connectionState);
// await after the connection has been closed, otherwise could deadlock on a user's .On callback(s)
await invocationMessageReceiveTask;
}
}

View File

@ -345,7 +345,7 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
var complete = await connection.ReadSentJsonAsync().OrTimeout();
Assert.Equal(HubProtocolConstants.CompletionMessageType, complete["type"]);
Assert.EndsWith("canceled by client.", ((string)complete["error"]));
}
}
}
[Fact]
@ -414,6 +414,59 @@ namespace Microsoft.AspNetCore.SignalR.Client.Tests
}
}
[Fact]
public async Task CanAwaitInvokeFromOnHandlerWithServerClosingConnection()
{
using (StartVerifiableLog())
{
var connection = new TestConnection();
var hubConnection = CreateHubConnection(connection, loggerFactory: LoggerFactory);
await hubConnection.StartAsync().OrTimeout();
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
hubConnection.On<string>("Echo", async msg =>
{
try
{
// This should be canceled when the connection is closed
await hubConnection.InvokeAsync<string>("Echo", msg).OrTimeout();
}
catch (Exception ex)
{
tcs.SetException(ex);
return;
}
tcs.SetResult(null);
});
var closedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
hubConnection.Closed += _ =>
{
closedTcs.SetResult(null);
return Task.CompletedTask;
};
await connection.ReceiveJsonMessage(new { type = HubProtocolConstants.InvocationMessageType, target = "Echo", arguments = new object[] { "42" } });
// Read sent message first to make sure invoke has been processed and is waiting for a response
await connection.ReadSentJsonAsync().OrTimeout();
await connection.ReceiveJsonMessage(new { type = HubProtocolConstants.CloseMessageType });
await closedTcs.Task.OrTimeout();
try
{
await tcs.Task.OrTimeout();
Assert.True(false);
}
catch (TaskCanceledException)
{
}
}
}
[Fact]
public async Task CanAwaitUsingHubConnection()
{