LocalDb: publish 0.3.8 (sync stream ends on ApplicationStopping, lmxopcua#906) and pin it in OtOpcUa + ScadaBridge #57

Closed
opened 2026-11-13 03:29:42 -05:00 by dohertj2 · 2 comments
Owner

Follow-up from lmxopcua#906 (planned stop of an OtOpcUa Primary took ~30 s on QA).

Library change (source only, NOT published): scadaproj 3ddb083 — LocalDbSyncService takes an optional IHostApplicationLifetime and links ApplicationStopping into the stream's token, so a graceful stop of the passive node ends the peer's open /localdb_sync.v1.LocalDbSync/Sync stream at once instead of letting Kestrel's drain wait out ShutdownTimeout (30 s default). A stream arriving once stopping has begun is refused Unavailable. Wire-compatible, no proto/schema change. 262/262; both halves mutation-killed. Directory.Build.props still says 0.3.7.

To do:

  1. Bump to 0.3.8, pack and publish the three packages to the Gitea feed (owner decision).
  2. OtOpcUa: pin 0.3.8. Its host-side mitigation (ShutdownStreamCancellationInterceptor, lmxopcua#906) can stay — it also covers the mesh telemetry/command streams — so the pin is belt-and-braces there.
  3. ScadaBridge has the same exposure on site pairs: Program.cs maps MapZbLocalDbSync() on the site Kestrel listener and cancels only its own SiteStreamGrpcServer streams on ApplicationStopping (CancelAllStreams). The LocalDb sync stream is not cancelled, and WebApplication stops Kestrel before AkkaHostedService, so stopping the passive node of a site pair should wait out Kestrel's drain (30 s) before CoordinatedShutdown/leave. Not changed and not measured in ScadaBridge. Pinning 0.3.8 fixes the stream part; whether to also leave the cluster before the drain (OtOpcUa's GracefulClusterLeaveService pattern) is ScadaBridge's call.

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi

Follow-up from lmxopcua#906 (planned stop of an OtOpcUa Primary took ~30 s on QA). **Library change (source only, NOT published):** scadaproj `3ddb083` — `LocalDbSyncService` takes an optional `IHostApplicationLifetime` and links `ApplicationStopping` into the stream's token, so a graceful stop of the passive node ends the peer's open `/localdb_sync.v1.LocalDbSync/Sync` stream at once instead of letting Kestrel's drain wait out `ShutdownTimeout` (30 s default). A stream arriving once stopping has begun is refused `Unavailable`. Wire-compatible, no proto/schema change. 262/262; both halves mutation-killed. `Directory.Build.props` still says `0.3.7`. **To do:** 1. Bump to `0.3.8`, pack and publish the three packages to the Gitea feed (owner decision). 2. OtOpcUa: pin `0.3.8`. Its host-side mitigation (`ShutdownStreamCancellationInterceptor`, lmxopcua#906) can stay — it also covers the mesh telemetry/command streams — so the pin is belt-and-braces there. 3. **ScadaBridge has the same exposure** on site pairs: `Program.cs` maps `MapZbLocalDbSync()` on the site Kestrel listener and cancels only its own `SiteStreamGrpcServer` streams on `ApplicationStopping` (`CancelAllStreams`). The LocalDb sync stream is not cancelled, and `WebApplication` stops Kestrel before `AkkaHostedService`, so stopping the passive node of a site pair should wait out Kestrel's drain (30 s) before CoordinatedShutdown/leave. Not changed and not measured in ScadaBridge. Pinning `0.3.8` fixes the stream part; whether to also leave the cluster before the drain (OtOpcUa's `GracefulClusterLeaveService` pattern) is ScadaBridge's call. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
dohertj2 added the type:followuppriority:p2cross-repo labels 2026-11-13 03:29:42 -05:00
Author
Owner

ScadaBridge side done in ScadaBridge#140 (main 1a6108c4, not deployed). A graceful stop now leaves the cluster before Kestrel drains, on both Central and Site (AkkaHostedService is an IHostedLifecycleService). A host-side ShutdownStreamCancellationInterceptor ends the LocalDb sync stream on ApplicationStopping. Test: stop went from 8859 ms to 960 ms with an 8 s drain budget, and the member is now removed before the drain starts. Item 3 here is covered without the 0.3.8 pin. Pinning 0.3.8 in ScadaBridge is still to-do 1/2 (belt-and-braces).

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi

ScadaBridge side done in ScadaBridge#140 (`main` 1a6108c4, not deployed). A graceful stop now leaves the cluster before Kestrel drains, on both Central and Site (`AkkaHostedService` is an `IHostedLifecycleService`). A host-side `ShutdownStreamCancellationInterceptor` ends the LocalDb sync stream on `ApplicationStopping`. Test: stop went from 8859 ms to 960 ms with an 8 s drain budget, and the member is now removed before the drain starts. Item 3 here is covered without the 0.3.8 pin. Pinning 0.3.8 in ScadaBridge is still to-do 1/2 (belt-and-braces). Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
Author
Owner

Done: ZB.MOM.WW.LocalDb 0.3.8 is published and pinned in both apps. Nothing is deployed.

Library: version bump scadaproj 14c29f4 (it ships 3ddb083). 262/262 tests, Release build 0 warnings. Docs updated in 276260a.

Feed (SHA256 of each file downloaded from the feed matches the local pack):

  • ZB.MOM.WW.LocalDb.0.3.8.nupkg 407ffd49e80530d6b2dd9354c645cccb70a794e5f778e43516dc2539580ffd47
  • ZB.MOM.WW.LocalDb.Contracts.0.3.8.nupkg b397b6d80e6bb33c299954c0c895609614e8b54010c0674587bf329e4ee1b674
  • ZB.MOM.WW.LocalDb.Replication.0.3.8.nupkg d3e37254d6115421816ba46ed53c55bb203be4f5baad6f9ca50df6ca4667f363

Pins (both apps keep their host-side ShutdownStreamCancellationInterceptor):

  • OtOpcUa master 47f0b98c: Release build 0 warnings. Host.Tests 288/288. Host.IntegrationTests 350 passed, 2 skipped (fixture-dependent Modbus). Runtime.Tests 1063 passed, 3 skipped. Core.AlarmHistorian.Tests 53/53.
  • ScadaBridge main 8e32f353: Release build 0 warnings. Host.Tests 733/733. IntegrationTests 133/133, including the LocalDb site-pair, Phase 2, config and store-and-forward convergence tests. Communication.Tests 843/843. HealthMonitoring.Tests 131/131. SiteEventLogging.Tests 118/118.

Unexpected, and fixed in the same pin commits: under 0.3.8, the positive control in each app's GracefulStopWithOpenSyncStreamTests (PositiveControl_WithoutTheFix_…) failed. The stop took about 0.9 s instead of the 8 s drain, because the library now ends the stream even without the host interceptor, so the end-to-end library fix works. To keep the control able to reproduce the bug, each control now registers LocalDbSyncService with lifetime: null, which is the 0.3.7 behaviour. The control is back at about 9 s and passes. In ScadaBridge I checked that the control fails on 0.3.8 without this change. Both fixed-path tests pass at about 1 s.

Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi

Done: **ZB.MOM.WW.LocalDb 0.3.8** is published and pinned in both apps. Nothing is deployed. **Library:** version bump scadaproj `14c29f4` (it ships `3ddb083`). 262/262 tests, Release build 0 warnings. Docs updated in `276260a`. **Feed** (SHA256 of each file downloaded from the feed matches the local pack): - `ZB.MOM.WW.LocalDb.0.3.8.nupkg` `407ffd49e80530d6b2dd9354c645cccb70a794e5f778e43516dc2539580ffd47` - `ZB.MOM.WW.LocalDb.Contracts.0.3.8.nupkg` `b397b6d80e6bb33c299954c0c895609614e8b54010c0674587bf329e4ee1b674` - `ZB.MOM.WW.LocalDb.Replication.0.3.8.nupkg` `d3e37254d6115421816ba46ed53c55bb203be4f5baad6f9ca50df6ca4667f363` **Pins** (both apps keep their host-side `ShutdownStreamCancellationInterceptor`): - OtOpcUa `master` `47f0b98c`: Release build 0 warnings. Host.Tests 288/288. Host.IntegrationTests 350 passed, 2 skipped (fixture-dependent Modbus). Runtime.Tests 1063 passed, 3 skipped. Core.AlarmHistorian.Tests 53/53. - ScadaBridge `main` `8e32f353`: Release build 0 warnings. Host.Tests 733/733. IntegrationTests 133/133, including the LocalDb site-pair, Phase 2, config and store-and-forward convergence tests. Communication.Tests 843/843. HealthMonitoring.Tests 131/131. SiteEventLogging.Tests 118/118. **Unexpected, and fixed in the same pin commits:** under 0.3.8, the positive control in each app's `GracefulStopWithOpenSyncStreamTests` (`PositiveControl_WithoutTheFix_…`) failed. The stop took about 0.9 s instead of the 8 s drain, because the library now ends the stream even without the host interceptor, so the end-to-end library fix works. To keep the control able to reproduce the bug, each control now registers `LocalDbSyncService` with `lifetime: null`, which is the 0.3.7 behaviour. The control is back at about 9 s and passes. In ScadaBridge I checked that the control fails on 0.3.8 without this change. Both fixed-path tests pass at about 1 s. Claude-Session: https://claude.ai/code/session_01GT67ACmpMR7b9LGRPLijMi
Sign in to join this conversation.