Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

SignalR performance: track groups per connection, remove on disconnect #53486

Merged
merged 10 commits into from Feb 7, 2024

Conversation

alex-jitbit
Copy link
Contributor

@alex-jitbit alex-jitbit commented Jan 19, 2024

Instead of iterating over ALL the connection-groups, which is slow and even introduces a DDoS vector, we remove from groups that are specific to this connection upon disconnect.

Signalr CPU optimization

Similar to RedisHubLifetimeManager the new DefaultHubLifetimeManager now makes every connection track groups per connection. So when a client disconnects there's no need to iterate a huge dictionary of all groups (there can be tens of thousands.

This change should also drastically speed up application shutdown. When you "recycle" application pool on IIS/Windows, or you gracefully end Kestrel process on Linux, or switch nginx-proxy between application instances in a blue-green scenario - you literally get thousands and thousands of "disconnected" events)

Fixes #48249

…net#48249

Instead of iterating over ALL the groups, which is slow and even introduces a DDoS vector, we remove from groups that are specific to this connection
@dotnet-issue-labeler dotnet-issue-labeler bot added the area-signalr Includes: SignalR clients and servers label Jan 19, 2024
@davidfowl
Copy link
Member

Does it need to be a feature? Can we just add a list of groups to the connection context? We can optimize with our own internal state.

@davidfowl
Copy link
Member

Can we also delete RemoveFromDisconnection since it's no longer used?

@alex-jitbit
Copy link
Contributor Author

@davidfowl

In case you're asking me:

Does it need to be a feature? Can we just add a list of groups to the connection context?

Performance-wise doesn't matter they're both just dictionaries under the hood.

Context.Items is what I used initially. Then I looked at your other implementations of HubLifetimeManager and saw that you mostly use features. So I though it'd be more "Microsofty" this way. I personally think features look better and the code becomes more readable. Context-items involve casting, type-checks and other ugly hacks.

Can we also delete RemoveFromDisconnection since it's no longer used?

What is RemoveFromDisconnection? Do you mean HubGroupList.RemoveDisconnectedConnection? Sure. However, it's public, so removing it might be a breaking change.

@davidfowl
Copy link
Member

Performance-wise doesn't matter they're both just dictionaries under the hood.

It's less dictionaries. Seems apt for our default implementation to be as optimized as it could be.

What is RemoveFromDisconnection? Do you mean HubGroupList.RemoveDisconnectedConnection? Sure. However, it's public, so removing it might be a breaking change.

It's an internal type.

@alex-jitbit
Copy link
Contributor Author

Sure, I'll remove the unused method ASAP, and rewrite it into connection.Context.Items if the reviewers agree on that.

P.S. I forgot to mention that this change also drastically speeds up application shutdown. When you "recycle" application pool on IIS/Windows, or you gracefully end Kestrel process on Linux, or you just switch nginx-proxy to a new application instance in a blue-green scenario (also on Linux) - you literally get thousands and thousands of "disconnect" events. And shutting down a SignalR app process results in CPU load jumping to 100% and stay there for 10-40 seconds (depending on the number of connections and how beefed up the server is). That is why I'm humbly suggesting releasing this change as a 8.0.x patch

@alex-jitbit alex-jitbit changed the title SignalR: track groups per connection, remove on disconnect, fixes dot… SignalR performance: track groups per connection, remove on disconnect Jan 19, 2024
src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs Outdated Show resolved Hide resolved
src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs Outdated Show resolved Hide resolved
src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs Outdated Show resolved Hide resolved
src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs Outdated Show resolved Hide resolved
src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs Outdated Show resolved Hide resolved
src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs Outdated Show resolved Hide resolved
src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs Outdated Show resolved Hide resolved
src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs Outdated Show resolved Hide resolved
src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs Outdated Show resolved Hide resolved
src/SignalR/server/Core/src/DefaultHubLifetimeManager.cs Outdated Show resolved Hide resolved
@BrennanConroy
Copy link
Member

Does it need to be a feature? Can we just add a list of groups to the connection context?

Performance-wise doesn't matter they're both just dictionaries under the hood.

Context.Items is what I used initially. Then I looked at your other implementations of HubLifetimeManager and saw that you mostly use features. So I though it'd be more "Microsofty" this way. I personally think features look better and the code becomes more readable. Context-items involve casting, type-checks and other ugly hacks.

I think he meant making the HashSet an internal property on HubConnectionContext. That way the collection is directly accessed instead of going through the IFeatureCollection dictionary. It also saves the allocation of the GroupTrackerFeature.

@alex-jitbit
Copy link
Contributor Author

@BrennanConroy oh, got it. Sure, just updated the PR!

@davidfowl
Copy link
Member

@BrennanConroy is this good to go? Do we need any other tests?

@BrennanConroy BrennanConroy merged commit 14868cc into dotnet:main Feb 7, 2024
25 checks passed
@dotnet-policy-service dotnet-policy-service bot added this to the 9.0-preview2 milestone Feb 7, 2024
@BrennanConroy
Copy link
Member

Thanks @alex-jitbit! I know the review comments were a bit spread out so thanks for sticking with it. As for backporting the changes I think that's not too bad since the only way to workaround it would be to do some private reflection.

@BrennanConroy
Copy link
Member

/backport to release/8.0

Copy link
Contributor

github-actions bot commented Feb 7, 2024

Started backporting to release/8.0: https://github.com/dotnet/aspnetcore/actions/runs/7809566574

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
area-signalr Includes: SignalR clients and servers
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Optimize DefaultHubLifetimeManager.OnDisconnectedAsync when there are thousands of unsubscribed groups
3 participants