* [SignalR] Avoid deadlock with closing and user callbacks (#19612) * fb
This commit is contained in:
parent
7582cfc593
commit
9514a865ef
|
|
@ -1207,11 +1207,13 @@ namespace Microsoft.AspNetCore.SignalR.Client
|
||||||
finally
|
finally
|
||||||
{
|
{
|
||||||
invocationMessageChannel.Writer.TryComplete();
|
invocationMessageChannel.Writer.TryComplete();
|
||||||
await invocationMessageReceiveTask;
|
|
||||||
timer.Stop();
|
timer.Stop();
|
||||||
await timerTask;
|
await timerTask;
|
||||||
uploadStreamSource.Cancel();
|
uploadStreamSource.Cancel();
|
||||||
await HandleConnectionClose(connectionState);
|
await HandleConnectionClose(connectionState);
|
||||||
|
|
||||||
|
// await after the connection has been closed, otherwise could deadlock on a user's .On callback(s)
|
||||||
|
await invocationMessageReceiveTask;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -414,6 +414,52 @@ 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" } }).OrTimeout();
|
||||||
|
|
||||||
|
// 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 }).OrTimeout();
|
||||||
|
|
||||||
|
await closedTcs.Task.OrTimeout();
|
||||||
|
|
||||||
|
await Assert.ThrowsAsync<TaskCanceledException>(() => tcs.Task.OrTimeout());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private class SampleObject
|
private class SampleObject
|
||||||
{
|
{
|
||||||
public SampleObject(string foo, int bar)
|
public SampleObject(string foo, int bar)
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue