LocalDb 0.3.8 ends open sync streams on ApplicationStopping and refuses
new ones Unavailable while stopping (lmxopcua#906, library side). The
host-side ShutdownStreamCancellationInterceptor stays: it also covers
the mesh telemetry/command streams.
GracefulStopWithOpenSyncStreamTests' positive control started passing
the stop in ~950 ms under 0.3.8, because the library alone now ends
the stream. The control registers LocalDbSyncService without the host
lifetime (the 0.3.7 behaviour), so it still reproduces the 8 s drain.
Release build 0 warnings. Host.Tests 288/288, Host.IntegrationTests
350 passed / 2 skipped, Runtime.Tests 1063 passed / 3 skipped,
Core.AlarmHistorian.Tests 53/53.
Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
A planned stop of the Primary took ~30 s on QA: WebApplication stops the web
server before Akka.Hosting's service, and Kestrel's graceful drain waited the
whole ShutdownTimeout for the peer's open LocalDb sync stream (the library's
LocalDbSyncService only observes the call token, which fires at the END of the
drain). Only then did CoordinatedShutdown run, so the stopping node held the
Primary role and the peer sat at ServiceLevel 240 for the whole window.
- GracefulClusterLeaveService (IHostedLifecycleService): StoppingAsync runs
before any hosted service's StopAsync; it raises ApplicationStopping, then
runs and awaits CoordinatedShutdown with ClrExitReason (not Cluster.Leave,
whose ClusterLeavingReason makes Akka.Hosting set ExitCode -1). The cluster
is left while Kestrel is still up.
- ShutdownStreamCancellationInterceptor (innermost on the shared gRPC
pipeline): every streaming call gets a context whose token is linked with
ApplicationStopping; streams arriving once stopping are refused Unavailable.
Host-side mitigation until LocalDb 0.3.8 is published and pinned
(scadaproj 3ddb083, scadaproj#57); it also covers the mesh streams.
Test GracefulStopWithOpenSyncStreamTests (real WebApplication + Kestrel h2c +
MapZbLocalDbSync + single-node Akka cluster + real peer initiator, 8 s drain
budget): stop 895 ms, cluster left at 892 ms, drain began at 894 ms. Positive
control without the fix: stop 8863 ms, cluster left at 8841 ms after the drain
began at 0 ms. Mutations: no CoordinatedShutdown in StoppingAsync -> order
assertion fails (drain at 1 ms, removed at 893 ms); interceptor passes the
original context -> stop 8006 ms, fails.
Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi