dotnet / dotnet/aspnetcore

DefaultHubLifetimeManager performs full enumeration of ConcurrentDictionary when calling SendUsersAsync (SendToAllConnections)

Open
#60,092 2 comments 3 reactions 0 assignees View on GitHub
area-signalr
Dominant language
C#
Stars
38.4k
Forks
10.9k
Avg merge
2d 10h
Merged PRs (30d)
281

Description

### Is there an existing issue for this?

- [x] I have searched the existing issues

### Is your feature request related to a problem? Please describe the problem.

Currently, when SendUsersAsync is called in DefaultHubLifetimeManager, it performs a full iteration over the ConcurrentDictionary, checking all connections instead of retrieving the data in O(1) time complexity.

This can lead to performance issues as the iteration scales poorly with the number of connections. Moreover, this behavior is not intuitive, since other hub lifetime managers, such as RedisHubLifetimeManager, create separate channels for each user instead of storing all users under a single key and performing a full scan.

### Describe the solution you'd like

I propose adding a separate dictionary to store connections by userId, similar to how groups and connections are already managed:
`private readonly HubConnectionStore _connections = new HubConnectionStore();
private readonly HubGroupList _groups = new HubGroupList();`
This would allow direct access to user connections in O(1) time, significantly improving performance, especially in high-load scenarios.

### Additional context

The current approach affects performance when scaling large SignalR applications.
RedisHubLifetimeManager already follows a per-user channel approach, making it more efficient.
Introducing a userId-based dictionary would align the behavior of DefaultHubLifetimeManager with RedisHubLifetimeManager, making the API more consistent.

Contributor guide

Open the contributing guide

Assessment

This issue has not been assessed yet.

Get new issues in your inbox

A short digest of beginner-friendly GitHub issues.