In this lesson, you will learn how to simulate high-concurrency scenarios to uncover memory bottlenecks and thread starvation in your SignalR Hubs.
When scaling SignalR, the primary challenge isn't just the message volume, but the overhead of maintaining thousands of open WebSocket connections. Each connection consumes a socket and a small amount of memory, but when your hub logic executes asynchronously, you risk thread starvation if you are blocking the thread pool instead of using async/await throughout your entire pipeline.
Identifying Bottlenecks
To test this, you shouldn't rely on simple HTTP load testers. You need a tool like Microsoft.AspNetCore.SignalR.Client to spawn multiple HubConnection instances across different worker processes. By spinning up 5,000+ connections, you can monitor the ThreadPool Starvation metric in your diagnostic tools (like dotnet-counters or App Insights).
If you see ThreadPool Queue Length climbing while CPU Usage remains relatively low, your hub methods are likely performing synchronous I/O or CPU-bound work that blocks the pool.
Simulated Stress Client
You can create a simple console application to act as your "load generator":
var connection = new HubConnectionBuilder()
.WithUrl("https://your-app.com/chat")
.Build();
await connection.StartAsync();
// Simulate high frequency hub method invocation
for (int i = 0; i < 1000; i++)
{
_ = connection.InvokeAsync("SendMessage", "Test", "LoadMessage");
}
By running multiple instances of this client, you force the server to allocate memory for each connection context. If the server’s memory usage grows linearly and doesn't stabilize, you have likely found a memory leak in your Hub state management or you are holding onto large objects in the HubContext longer than necessary.
Watch for "Connection Timeouts" in your client logs; these are the smoking gun for thread starvation, indicating that the server is too busy to process the initial handshake or the ping/pong heartbeats because the thread pool is exhausted.
Key Takeaway: Load testing SignalR requires monitoring ThreadPool queue depth and memory stability under massive concurrent connection counts, rather than just measuring raw message throughput.