AB CIP PR 1 — Extract shared PollGroupEngine into Core.Abstractions #108

Merged
dohertj2 merged 1 commits from abcip-pr1-pollgroupengine into v2 2026-04-19 15:51:12 -04:00
Owner

Summary

First PR of the AB CIP driver sequence (13 PRs planned). Lifts the Modbus subscription poll loop into a shared PollGroupEngine class so AB CIP (and later S7/FOCAS/AB Legacy) can reuse it.

Behaviour-preserving refactor:

  • SubscriptionState + PollLoopAsync + PollOnceAsync + subscription handle extracted into src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/PollGroupEngine.cs
  • ModbusDriver ISubscribable surface now delegates Subscribe/Unsubscribe into the engine
  • ShutdownAsync calls engine DisposeAsync
  • Initial-data push semantics preserved (forceRaise=true on first poll)
  • Exception-tolerant loop preserved
  • Interval floor (100 ms default) is now a ctor knob per driver

Placement rationale

Lives in Core.Abstractions, not Core. Driver projects only reference Core.Abstractions by convention (Modbus, OpcUaClient, S7 csproj shape); putting the engine in Core would drag EF Core + Serilog + Polly into every driver. PollGroupEngine has no dependencies beyond System.Collections.Concurrent + System.Threading, so Core.Abstractions stays lightweight.

Test plan

  • All 177 ModbusDriver.Tests pass unmodified
  • 10 new direct engine tests in Core.Abstractions.Tests:
    • initial force-raise, unchanged-value single-raise, change-between-polls
    • unsubscribe halts loop, interval-floor clamp
    • independent subscriptions, reader-exception tolerance
    • unknown-handle returns false, ActiveSubscriptionCount lifecycle
    • DisposeAsync cancels all
  • Full solution builds (0 errors)

Unblocks

  • AB CIP PR 7 — ISubscribable consumes the engine
  • S7 + FOCAS can drop their own poll loops when they re-base

Merges to v2.

## Summary First PR of the AB CIP driver sequence (13 PRs planned). Lifts the Modbus subscription poll loop into a shared `PollGroupEngine` class so AB CIP (and later S7/FOCAS/AB Legacy) can reuse it. Behaviour-preserving refactor: - `SubscriptionState` + `PollLoopAsync` + `PollOnceAsync` + subscription handle extracted into `src/ZB.MOM.WW.OtOpcUa.Core.Abstractions/PollGroupEngine.cs` - `ModbusDriver` ISubscribable surface now delegates Subscribe/Unsubscribe into the engine - `ShutdownAsync` calls engine `DisposeAsync` - Initial-data push semantics preserved (`forceRaise=true` on first poll) - Exception-tolerant loop preserved - Interval floor (100 ms default) is now a ctor knob per driver ## Placement rationale Lives in `Core.Abstractions`, not `Core`. Driver projects only reference `Core.Abstractions` by convention (Modbus, OpcUaClient, S7 csproj shape); putting the engine in `Core` would drag EF Core + Serilog + Polly into every driver. `PollGroupEngine` has no dependencies beyond `System.Collections.Concurrent` + `System.Threading`, so `Core.Abstractions` stays lightweight. ## Test plan - [x] All 177 ModbusDriver.Tests pass unmodified - [x] 10 new direct engine tests in Core.Abstractions.Tests: - initial force-raise, unchanged-value single-raise, change-between-polls - unsubscribe halts loop, interval-floor clamp - independent subscriptions, reader-exception tolerance - unknown-handle returns false, ActiveSubscriptionCount lifecycle - DisposeAsync cancels all - [x] Full solution builds (0 errors) ## Unblocks - AB CIP PR 7 — `ISubscribable` consumes the engine - S7 + FOCAS can drop their own poll loops when they re-base Merges to `v2`.
dohertj2 added 1 commit 2026-04-19 15:37:12 -04:00
AB CIP PR 1 — extract shared PollGroupEngine into Core.Abstractions so the AB CIP driver (and any other poll-based driver — S7, FOCAS, AB Legacy) can reuse the subscription loop instead of reimplementing it. Behaviour-preserving refactor of ModbusDriver: SubscriptionState + PollLoopAsync + PollOnceAsync + ModbusSubscriptionHandle lifted verbatim into a new PollGroupEngine class, ModbusDriver's ISubscribable surface now delegates Subscribe/Unsubscribe into the engine and ShutdownAsync calls engine DisposeAsync. Interval floor (100 ms default) becomes a PollGroupEngine constructor knob so per-driver tuning is possible without re-shipping the loop. Initial-data push semantics preserved via forceRaise=true on the first poll. Exception-tolerant loop preserved — reader throws are swallowed, loop continues, driver's health surface remains the single reporting path. Placement in Core.Abstractions (not Core) because driver projects only reference Core.Abstractions by convention (matches OpcUaClient / Modbus / S7 csproj shape); putting the engine in Core would drag EF Core + Serilog + Polly into every driver. Module has no new dependencies beyond System.Collections.Concurrent + System.Threading, so Core.Abstractions stays lightweight. Modbus ctor converted from primary to explicit so the engine field can capture this for the reader + on-change bridge. All 177 ModbusDriver.Tests pass unmodified (Modbus subscription suite, probe suite, cap suite, exception mapper, reconnect, TCP). 10 new direct engine tests in Core.Abstractions.Tests covering: initial force-raise, unchanged-value single-raise, change-between-polls, unsubscribe halts loop, interval-floor clamp, independent subscriptions, reader-exception tolerance, unknown-handle returns false, ActiveSubscriptionCount lifecycle, DisposeAsync cancels all. No changes to driver-specs.md nor to the server Hosting layer — engine is a pure internal building block at this stage. Unblocks AB CIP PR 7 (ISubscribable consumes the engine); also sets up S7 + FOCAS to drop their own poll loops when they re-base. 4ab587707f
Co-Authored-By: Claude Opus 4.7 (1M context) <noreply@anthropic.com>
dohertj2 merged commit bff6651b4b into v2 2026-04-19 15:51:12 -04:00
Sign in to join this conversation.
No Reviewers
No Label
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: dohertj2/lmxopcua#108