fix(dashboard): parallel bounded alarm drains, best-effort disposal catch-alls, ConfigureAwait alignment

This commit is contained in:
Joseph Doherty
2026-08-16 04:20:59 -04:00
parent 3faa272db9
commit 7cfb2e727d
2 changed files with 23 additions and 6 deletions
@@ -323,6 +323,14 @@
catch (OperationCanceledException)
{
}
catch
{
// Catch-all for the same reason ProviderStatusLoopAsync has one: a teardown race
// can fault InvokeAsync (a disposed renderer) after cancellation has already been
// requested. Letting that fault the task would surface it out of the drain in
// DisposeAsync, skipping _cts.Dispose(). The loop ends here and the page holds its
// last rendered rows — it is being disposed or has nothing left to poll with.
}
}
private async Task RefreshAlarmsAsync()
@@ -341,10 +349,13 @@
/// <inheritdoc />
public async ValueTask DisposeAsync()
{
await _cts.CancelAsync();
await _cts.CancelAsync().ConfigureAwait(false);
await DrainAsync(_pollTask);
await DrainAsync(_providerStatusTask);
// Drained together, not one after the other: the wedged dispatcher this bound exists
// for blocks both loops at once, so sequential drains would time out twice and make
// the real bound 10 seconds. DrainAsync tolerates a null task.
await Task.WhenAll(DrainAsync(_pollTask), DrainAsync(_providerStatusTask))
.ConfigureAwait(false);
_cts.Dispose();
GC.SuppressFinalize(this);
@@ -362,7 +373,7 @@
try
{
await loop.WaitAsync(LoopDrainTimeout);
await loop.WaitAsync(LoopDrainTimeout).ConfigureAwait(false);
}
catch (TimeoutException)
{
@@ -370,5 +381,9 @@
catch (OperationCanceledException)
{
}
catch
{
// Other disposal-time errors are best-effort.
}
}
}