Skip to content

Commit bdbd1b2

Browse files
spewuclaude
andcommitted
Fix critical SignalR connection recovery issue
This commit fixes a critical bug where the SignalR connection would not recover after the automatic reconnect mechanism exhausted its retry attempts. This caused services using SignalRHostedService to permanently lose connectivity and require manual restart. Changes: - Added automatic reconnection logic in the Closed event handler - When connection closes unexpectedly, it now triggers ExecuteStartUp() which uses the Polly retry policy with infinite retries - Added semaphore lock to prevent multiple simultaneous reconnection attempts - Added isShuttingDown flag to prevent reconnection during graceful shutdown - Improved logging for connection lifecycle events This resolves the issue where services would crash with WebSocketException and require manual restart when network connectivity was temporarily lost. Version bumped to 2.2.3 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <[email protected]>
1 parent d0bbb1e commit bdbd1b2

File tree

2 files changed

+43
-5
lines changed

2 files changed

+43
-5
lines changed

ProReception.DistributionServerInfrastructure/HostedServices/SignalRHostedService.cs

Lines changed: 42 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,9 +25,11 @@ public abstract class SignalRHostedService<T>(
2525
: IHostedService
2626
{
2727
private readonly CancellationTokenSource stoppingCts = new();
28+
private readonly SemaphoreSlim reconnectLock = new(1, 1);
2829

2930
private Task? startUpTask;
3031
private HubConnection? hubConnection;
32+
private bool isShuttingDown;
3133

3234
protected abstract string HubPath { get; }
3335

@@ -51,6 +53,9 @@ public async Task StopAsync(CancellationToken cancellationToken)
5153
{
5254
logger.LogInformation($"Stopping {typeof(T).Name}...");
5355

56+
// Set flag to prevent reconnection attempts during shutdown
57+
isShuttingDown = true;
58+
5459
// Stop called without start
5560
if (startUpTask == null)
5661
{
@@ -167,11 +172,44 @@ private async Task LoginAndCreateSignalRConnection(CancellationToken cancellatio
167172
return Task.CompletedTask;
168173
};
169174

170-
hubConnection.Closed += error =>
175+
hubConnection.Closed += async error =>
171176
{
172-
// Do not call StopAsync or recursively restart; rely on automatic reconnect and outer retry/startup logic
173-
logger.LogInformation(error, "SignalR connection closed. Waiting for background retry logic...");
174-
return Task.CompletedTask;
177+
if (isShuttingDown)
178+
{
179+
logger.LogInformation("SignalR connection closed during shutdown, not reconnecting");
180+
return;
181+
}
182+
183+
logger.LogWarning(error, "SignalR connection closed unexpectedly. Attempting to reconnect...");
184+
185+
// Use semaphore to prevent multiple simultaneous reconnection attempts
186+
if (await reconnectLock.WaitAsync(0))
187+
{
188+
try
189+
{
190+
// Dispose the old connection
191+
if (hubConnection != null)
192+
{
193+
await hubConnection.DisposeAsync();
194+
hubConnection = null;
195+
}
196+
197+
// Restart the connection with retry logic
198+
await ExecuteStartUp(stoppingCts.Token);
199+
}
200+
catch (Exception ex)
201+
{
202+
logger.LogError(ex, "Failed to reconnect SignalR after connection closed. Will be retried by Polly policy");
203+
}
204+
finally
205+
{
206+
reconnectLock.Release();
207+
}
208+
}
209+
else
210+
{
211+
logger.LogInformation("Reconnection already in progress, skipping duplicate attempt");
212+
}
175213
};
176214

177215
await hubConnection.StartAsync(cancellationToken);

version.props

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
<Project>
22
<PropertyGroup>
3-
<Version>2.2.2</Version>
3+
<Version>2.2.3</Version>
44
</PropertyGroup>
55
</Project>

0 commit comments

Comments
 (0)