fix(dashboard): parallel bounded alarm drains, best-effort disposal catch-alls, ConfigureAwait alignment
This commit is contained in:
@@ -246,9 +246,11 @@ the pill therefore reports is the case it exists for — the channel completing
|
|||||||
a page that is still watching.
|
a page that is still watching.
|
||||||
|
|
||||||
`AlarmsPage` owns two loops of its own (the 3 s alarm poll and the provider-status
|
`AlarmsPage` owns two loops of its own (the 3 s alarm poll and the provider-status
|
||||||
badge) and bounds each drain at 5 seconds on dispose, for the same reason
|
badge) and bounds their drain at 5 seconds on dispose, for the same reason
|
||||||
`DashboardPageBase` bounds its watch drain: both loops render through the renderer's
|
`DashboardPageBase` bounds its watch drain: both loops render through the renderer's
|
||||||
dispatcher, and disposal can run on it.
|
dispatcher, and disposal can run on it. The two are drained concurrently, so the
|
||||||
|
bound on disposal is 5 seconds in total rather than per loop — a wedged dispatcher
|
||||||
|
blocks both loops at once, and draining them in sequence would time out twice.
|
||||||
|
|
||||||
### SignalR hubs (remote clients)
|
### SignalR hubs (remote clients)
|
||||||
|
|
||||||
|
|||||||
@@ -323,6 +323,14 @@
|
|||||||
catch (OperationCanceledException)
|
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()
|
private async Task RefreshAlarmsAsync()
|
||||||
@@ -341,10 +349,13 @@
|
|||||||
/// <inheritdoc />
|
/// <inheritdoc />
|
||||||
public async ValueTask DisposeAsync()
|
public async ValueTask DisposeAsync()
|
||||||
{
|
{
|
||||||
await _cts.CancelAsync();
|
await _cts.CancelAsync().ConfigureAwait(false);
|
||||||
|
|
||||||
await DrainAsync(_pollTask);
|
// Drained together, not one after the other: the wedged dispatcher this bound exists
|
||||||
await DrainAsync(_providerStatusTask);
|
// 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();
|
_cts.Dispose();
|
||||||
GC.SuppressFinalize(this);
|
GC.SuppressFinalize(this);
|
||||||
@@ -362,7 +373,7 @@
|
|||||||
|
|
||||||
try
|
try
|
||||||
{
|
{
|
||||||
await loop.WaitAsync(LoopDrainTimeout);
|
await loop.WaitAsync(LoopDrainTimeout).ConfigureAwait(false);
|
||||||
}
|
}
|
||||||
catch (TimeoutException)
|
catch (TimeoutException)
|
||||||
{
|
{
|
||||||
@@ -370,5 +381,9 @@
|
|||||||
catch (OperationCanceledException)
|
catch (OperationCanceledException)
|
||||||
{
|
{
|
||||||
}
|
}
|
||||||
|
catch
|
||||||
|
{
|
||||||
|
// Other disposal-time errors are best-effort.
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user