Skip to content

Commit 9514a86

Browse files
[SignalR] Avoid deadlock with closing and user callbacks (#19612) (#19664)
* [SignalR] Avoid deadlock with closing and user callbacks (#19612) * fb
1 parent 7582cfc commit 9514a86

File tree

2 files changed

+50
-2
lines changed

2 files changed

+50
-2
lines changed

src/SignalR/clients/csharp/Client.Core/src/HubConnection.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1207,11 +1207,13 @@ async Task StartProcessingInvocationMessages(ChannelReader<InvocationMessage> in
12071207
finally
12081208
{
12091209
invocationMessageChannel.Writer.TryComplete();
1210-
await invocationMessageReceiveTask;
12111210
timer.Stop();
12121211
await timerTask;
12131212
uploadStreamSource.Cancel();
12141213
await HandleConnectionClose(connectionState);
1214+
1215+
// await after the connection has been closed, otherwise could deadlock on a user's .On callback(s)
1216+
await invocationMessageReceiveTask;
12151217
}
12161218
}
12171219

src/SignalR/clients/csharp/Client/test/UnitTests/HubConnectionTests.cs

Lines changed: 47 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -345,7 +345,7 @@ public async Task UploadStreamCancelationSendsStreamComplete()
345345
var complete = await connection.ReadSentJsonAsync().OrTimeout();
346346
Assert.Equal(HubProtocolConstants.CompletionMessageType, complete["type"]);
347347
Assert.EndsWith("canceled by client.", ((string)complete["error"]));
348-
}
348+
}
349349
}
350350

351351
[Fact]
@@ -414,6 +414,52 @@ bool ExpectedErrors(WriteContext writeContext)
414414
}
415415
}
416416

417+
[Fact]
418+
public async Task CanAwaitInvokeFromOnHandlerWithServerClosingConnection()
419+
{
420+
using (StartVerifiableLog())
421+
{
422+
var connection = new TestConnection();
423+
var hubConnection = CreateHubConnection(connection, loggerFactory: LoggerFactory);
424+
await hubConnection.StartAsync().OrTimeout();
425+
426+
var tcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
427+
hubConnection.On<string>("Echo", async msg =>
428+
{
429+
try
430+
{
431+
// This should be canceled when the connection is closed
432+
await hubConnection.InvokeAsync<string>("Echo", msg).OrTimeout();
433+
}
434+
catch (Exception ex)
435+
{
436+
tcs.SetException(ex);
437+
return;
438+
}
439+
440+
tcs.SetResult(null);
441+
});
442+
443+
var closedTcs = new TaskCompletionSource<object>(TaskCreationOptions.RunContinuationsAsynchronously);
444+
hubConnection.Closed += _ =>
445+
{
446+
closedTcs.SetResult(null);
447+
448+
return Task.CompletedTask;
449+
};
450+
451+
await connection.ReceiveJsonMessage(new { type = HubProtocolConstants.InvocationMessageType, target = "Echo", arguments = new object[] { "42" } }).OrTimeout();
452+
453+
// Read sent message first to make sure invoke has been processed and is waiting for a response
454+
await connection.ReadSentJsonAsync().OrTimeout();
455+
await connection.ReceiveJsonMessage(new { type = HubProtocolConstants.CloseMessageType }).OrTimeout();
456+
457+
await closedTcs.Task.OrTimeout();
458+
459+
await Assert.ThrowsAsync<TaskCanceledException>(() => tcs.Task.OrTimeout());
460+
}
461+
}
462+
417463
private class SampleObject
418464
{
419465
public SampleObject(string foo, int bar)

0 commit comments

Comments
 (0)