On a roll the old pod keeps serving its open Blazor circuits while it drains. The mesh's teardown
then drains the I/O pool (IoPoolRegistry.DrainAll), and every read still in flight ends with a
cooperative cancellation: TaskCanceledException: A task was canceled. The circuit is still open, so
the cancellation reaches views that are not yet disposed. BlazorView.SurfaceError used to treat
only the view's own disposal as teardown. It therefore logged the process's teardown at Error and
showed the error modal.
Measured once (MeshWeaver#7020): during a roll of an AKS instance, three lines
fail: ThreadChatView … Resolving the composer's context arrived within the same millisecond, from
two ThreadChatViews of one circuit (areas Composer and Thread/1). They came from the old pod, while the
roll's "Verify one generation" phase was running.
| The fault is… | while… | SurfaceError does |
|---|---|---|
| anything | the view is disposed, or it is an ObjectDisposedException |
Debug, no modal (unchanged) |
a cooperative cancellation (CancellationClassifier.IsCooperativeCancellation) |
the portal hub is shutting down or the host has begun stopping (hub.IsLeaving()) |
Debug, no modal |
| a cooperative cancellation | the host keeps running | Error, the modal: a binding ended for no stated reason |
a cancellation that expresses a timeout (a TimeoutException inside it) |
anything | Error, the modal: an availability failure |
The classifier is BlazorView.IsHostTeardownCancellation(error, hostLeaving). The probe is asked
only for a cooperative cancellation. A hub whose scope is already disposed counts as leaving.
Every view that routes its onError through SurfaceError gets this arm, directly or through
SurfaceReadError or the data-binding fault path. ThreadChatView's composer context chip is one of them.
Regression tests: HostTeardownCancellationTest.AReadCancelledByTheTeardownOfAStoppingHost_RaisesNoErrorModal
and its control, AReadCancelledWhileTheHostKeepsRunning_StillRaisesTheErrorModal. Both bind a view
to a leaf of a real IoPool and end it with a real Drain(). The control proves the silence is the
probe-gated arm, not a fault that never arrived. OnlyACooperativeCancellationOnALeavingHost_IsHostTeardown
pins the predicate's boundary.