Skip to content

Commit c7c3fc7

Browse files
Fix AddToGroupAsync race with OnDisconnectedAsync (#42117)
1 parent f9b5e13 commit c7c3fc7

File tree

3 files changed

+23
-9
lines changed

3 files changed

+23
-9
lines changed

src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,11 @@ public override Task AddToGroupAsync(string connectionId, string groupName, Canc
5151
}
5252

5353
_groups.Add(connection, groupName);
54+
// Connection disconnected while adding to group, remove it in case the Add was called after OnDisconnectedAsync removed items from the group
55+
if (connection.ConnectionAborted.IsCancellationRequested)
56+
{
57+
_groups.Remove(connection.ConnectionId, groupName);
58+
}
5459

5560
return Task.CompletedTask;
5661
}

src/SignalR/server/StackExchangeRedis/src/Internal/RedisSubscriptionManager.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
// Licensed to the .NET Foundation under one or more agreements.
1+
// Licensed to the .NET Foundation under one or more agreements.
22
// The .NET Foundation licenses this file to you under the MIT license.
33

44
using System.Collections.Concurrent;
@@ -16,6 +16,13 @@ public async Task AddSubscriptionAsync(string id, HubConnectionContext connectio
1616

1717
try
1818
{
19+
// Avoid adding subscription if connection is closing/closed
20+
// We're in a lock and ConnectionAborted is triggered before OnDisconnectedAsync is called so this is guaranteed to be safe when adding while connection is closing and removing items
21+
if (connection.ConnectionAborted.IsCancellationRequested)
22+
{
23+
return;
24+
}
25+
1926
var subscription = _subscriptions.GetOrAdd(id, _ => new HubConnectionStore());
2027

2128
subscription.Add(connection);
@@ -32,7 +39,7 @@ public async Task AddSubscriptionAsync(string id, HubConnectionContext connectio
3239
}
3340
}
3441

35-
public async Task RemoveSubscriptionAsync(string id, HubConnectionContext connection, Func<string, Task> unsubscribeMethod)
42+
public async Task RemoveSubscriptionAsync(string id, HubConnectionContext connection, object state, Func<object, string, Task> unsubscribeMethod)
3643
{
3744
await _lock.WaitAsync();
3845

@@ -48,7 +55,7 @@ public async Task RemoveSubscriptionAsync(string id, HubConnectionContext connec
4855
if (subscription.Count == 0)
4956
{
5057
_subscriptions.TryRemove(id, out _);
51-
await unsubscribeMethod(id);
58+
await unsubscribeMethod(state, id);
5259
}
5360
}
5461
finally

src/SignalR/server/StackExchangeRedis/src/RedisHubLifetimeManager.cs

Lines changed: 8 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -354,10 +354,11 @@ private async Task RemoveGroupAsyncCore(HubConnectionContext connection, string
354354
{
355355
var groupChannel = _channels.Group(groupName);
356356

357-
await _groups.RemoveSubscriptionAsync(groupChannel, connection, channelName =>
357+
await _groups.RemoveSubscriptionAsync(groupChannel, connection, this, static (state, channelName) =>
358358
{
359-
RedisLog.Unsubscribe(_logger, channelName);
360-
return _bus!.UnsubscribeAsync(channelName);
359+
var lifetimeManager = (RedisHubLifetimeManager<THub>)state;
360+
RedisLog.Unsubscribe(lifetimeManager._logger, channelName);
361+
return lifetimeManager._bus!.UnsubscribeAsync(channelName);
361362
});
362363

363364
var feature = connection.Features.GetRequiredFeature<IRedisFeature>();
@@ -386,10 +387,11 @@ private Task RemoveUserAsync(HubConnectionContext connection)
386387
{
387388
var userChannel = _channels.User(connection.UserIdentifier!);
388389

389-
return _users.RemoveSubscriptionAsync(userChannel, connection, channelName =>
390+
return _users.RemoveSubscriptionAsync(userChannel, connection, this, static (state, channelName) =>
390391
{
391-
RedisLog.Unsubscribe(_logger, channelName);
392-
return _bus!.UnsubscribeAsync(channelName);
392+
var lifetimeManager = (RedisHubLifetimeManager<THub>)state;
393+
RedisLog.Unsubscribe(lifetimeManager._logger, channelName);
394+
return lifetimeManager._bus!.UnsubscribeAsync(channelName);
393395
});
394396
}
395397

0 commit comments

Comments
 (0)