fix(ui): harden worker auto-reconnect lifecycle
- Fix _startCts lifecycle leak: old CTS is cancelled and disposed before replacement - Guard StartAsync re-entrancy: early-return if retry loop is still running (lock + IsCompleted check) - Await _retryLoopTask in both StopAsync and DisposeAsync before stopping/disposing the hub Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -26,6 +26,8 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable
|
|||||||
{
|
{
|
||||||
private readonly HubConnection _hub;
|
private readonly HubConnection _hub;
|
||||||
private CancellationTokenSource? _startCts;
|
private CancellationTokenSource? _startCts;
|
||||||
|
private Task _retryLoopTask = Task.CompletedTask;
|
||||||
|
private readonly object _startLock = new();
|
||||||
|
|
||||||
[ObservableProperty]
|
[ObservableProperty]
|
||||||
private bool _isConnected;
|
private bool _isConnected;
|
||||||
@@ -109,8 +111,18 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable
|
|||||||
|
|
||||||
public Task StartAsync()
|
public Task StartAsync()
|
||||||
{
|
{
|
||||||
_startCts = new CancellationTokenSource();
|
lock (_startLock)
|
||||||
_ = ConnectWithRetryAsync(_startCts.Token);
|
{
|
||||||
|
if (!_retryLoopTask.IsCompleted)
|
||||||
|
return Task.CompletedTask;
|
||||||
|
|
||||||
|
var old = _startCts;
|
||||||
|
_startCts = new CancellationTokenSource();
|
||||||
|
old?.Cancel();
|
||||||
|
old?.Dispose();
|
||||||
|
|
||||||
|
_retryLoopTask = ConnectWithRetryAsync(_startCts.Token);
|
||||||
|
}
|
||||||
return Task.CompletedTask;
|
return Task.CompletedTask;
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -144,6 +156,7 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable
|
|||||||
public async Task StopAsync()
|
public async Task StopAsync()
|
||||||
{
|
{
|
||||||
_startCts?.Cancel();
|
_startCts?.Cancel();
|
||||||
|
try { await _retryLoopTask; } catch (OperationCanceledException) { } catch { /* swallow */ }
|
||||||
try { await _hub.StopAsync(); } catch { /* swallow */ }
|
try { await _hub.StopAsync(); } catch { /* swallow */ }
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -183,6 +196,7 @@ public partial class WorkerClient : ObservableObject, IAsyncDisposable
|
|||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
_startCts?.Cancel();
|
_startCts?.Cancel();
|
||||||
|
try { await _retryLoopTask; } catch (OperationCanceledException) { } catch { /* swallow */ }
|
||||||
await _hub.DisposeAsync();
|
await _hub.DisposeAsync();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user