Promote V2 SubscriptionManager to ISession.TryGetSubscriptionManager (#3925)#3933
Conversation
…3925) The V2 options-based subscription manager is backed by the session's subscription engine, so it is a Session capability rather than a managed-only one. Expose it on ISession via a non-throwing bool TryGetSubscriptionManager(out ISubscriptionManager? manager) returning true + the manager for V2-engine sessions and false for classic-engine sessions (the unmanaged Session defaults to classic). - ISession: add TryGetSubscriptionManager. - Session (unmanaged): implement off the active subscription engine. - ManagedSession: implement by delegating to the inner session; the throwing ManagedSession.SubscriptionManager property is removed. - Migrate all client-side callers of the removed property to TryGetSubscriptionManager: public ManagedSessionExtensions helpers (via a private GetSubscriptionManager resolver, signatures unchanged), ManagedSession.Streaming, and the client subscription/session/stress test suites (via a RequireSubscriptionManager test helper). PubSub adapter: ServerSession reaches SubscriptionManager through TryGetSubscriptionManager and MessageContext through ISession directly (MessageContext was already on ISession via IClientBase), so all three (ManagedSession) downcasts and the CA1859 suppression are removed; CreateSessionAsync returns ISession. Docs: Sessions.md / Subscriptions.md updated; migrate/2.0.x/ sessions-subscriptions.md documents the property removal. Closes #3925
There was a problem hiding this comment.
Pull request overview
Note
Copilot couldn't run its full agentic review because no GitHub Actions runner was available. Make sure your repository has a runner available to run Copilot's review, or add a copilot-setup-steps.yml file specifying one with the runs-on attribute. See the docs for more details.
Promotes the V2 options-based subscription manager capability to ISession via TryGetSubscriptionManager, removing ManagedSession.SubscriptionManager and updating product code, tests, and docs to avoid downcasts to ManagedSession.
Changes:
- Added
ISession.TryGetSubscriptionManager(out ISubscriptionManager?)and implemented it inSessionandManagedSession. - Updated PubSub adapter and client helpers to resolve the V2 manager via
TryGetSubscriptionManagerinstead of(ManagedSession)casts / concrete access. - Migrated tests and docs to use
TryGetSubscriptionManager(and a test-onlyRequireSubscriptionManager()helper).
Reviewed changes
Copilot reviewed 18 out of 18 changed files in this pull request and generated 4 comments.
Show a summary per file
| File | Description |
|---|---|
| Tests/Opc.Ua.Subscriptions.Tests/TransferSubscriptionTests.cs | Migrates V2 subscription persistence calls to RequireSubscriptionManager() helper. |
| Tests/Opc.Ua.Subscriptions.Tests/SubscriptionTests.cs | Updates V2 assertions and save/load to use RequireSubscriptionManager(). |
| Tests/Opc.Ua.Subscriptions.Tests/SubscriptionEngineIntegrationTests.cs | Updates integration tests to use RequireSubscriptionManager() for V2-only behavior. |
| Tests/Opc.Ua.Subscriptions.Tests/SubscriptionCoverageTests.cs | Switches V2 restore tests to RequireSubscriptionManager() (still casts to concrete type). |
| Tests/Opc.Ua.Subscriptions.Tests/ManagedSessionSubscriptionManagerIntegrationTests.cs | Validates TryGetSubscriptionManager behavior for V2 vs classic engine sessions. |
| Tests/Opc.Ua.Stress.Tests/Channels/Chaos/SubscriptionSurvivalChaosTests.cs | Uses RequireSubscriptionManager() in stress readiness checks. |
| Tests/Opc.Ua.Sessions.Tests/ManagedSessionReconnectIntegrationTests.cs | Updates concrete manager access to flow through RequireSubscriptionManager(). |
| Tests/Opc.Ua.Client.TestFramework/Extensions.cs | Adds RequireSubscriptionManager() test helper that throws when V2 manager isn’t available. |
| Libraries/Opc.Ua.PubSub.Adapter/Session/ServerSession.cs | Removes ManagedSession downcasts; uses TryGetSubscriptionManager; CreateSessionAsync now returns ISession. |
| Libraries/Opc.Ua.Client/Session/Session.cs | Implements ISession.TryGetSubscriptionManager by checking the active subscription engine. |
| Libraries/Opc.Ua.Client/Session/ManagedSessionOptions.cs | Updates docs/comments to reference ISession.TryGetSubscriptionManager. |
| Libraries/Opc.Ua.Client/Session/ManagedSession.cs | Removes throwing SubscriptionManager property; delegates TryGetSubscriptionManager to inner ISession. |
| Libraries/Opc.Ua.Client/Session/ManagedSession.Streaming.cs | Resolves the V2 manager via TryGetSubscriptionManager and throws if classic engine is used. |
| Libraries/Opc.Ua.Client/Session/ISession.cs | Adds the TryGetSubscriptionManager API contract and documentation. |
| Libraries/Opc.Ua.Client/Fluent/ManagedSessionExtensions.cs | Switches fluent helpers to resolve the manager via a private resolver using TryGetSubscriptionManager. |
| Docs/migrate/2.0.x/sessions-subscriptions.md | Documents the breaking removal of ManagedSession.SubscriptionManager and the new ISession API. |
| Docs/Subscriptions.md | Updates docs to use TryGetSubscriptionManager instead of ManagedSession.SubscriptionManager. |
| Docs/Sessions.md | Updates session/subscription-engine docs to reference TryGetSubscriptionManager. |
- ServerSession.CreateDataChangeSubscriptionAsync: expand the BadNotSupported message to name the V2-engine requirement (DefaultSubscriptionEngineFactory) and include the endpoint URL for troubleshooting. - ServerSession.StartModelChangeMonitoringAsync: clarify the classic-engine log message (optional/best-effort monitoring, V2-engine guidance, endpoint context); the no-op remains intentional since model-change monitoring is best-effort (unlike required data-change subscriptions which throw). - ManagedSessionExtensions.GetSubscriptionManager: add a null-check on the session argument, matching the file's public helpers. - SubscriptionCoverageTests: assert the manager is a concrete SubscriptionManager (Is.InstanceOf) before downcasting, so a type mismatch fails diagnosably rather than as an unexpected InvalidCastException.
Codecov Report❌ Patch coverage is Additional details and impacted files@@ Coverage Diff @@
## master #3933 +/- ##
=======================================
Coverage 73.52% 73.52%
=======================================
Files 1169 1169
Lines 169907 169942 +35
Branches 29296 29302 +6
=======================================
+ Hits 124927 124954 +27
+ Misses 34005 34004 -1
- Partials 10975 10984 +9
🚀 New features to boost your workflow:
|
…Null in test helper ArgumentNullException.ThrowIfNull is unavailable on net472/net48/netstandard2.0, which broke build-windows-all-tfm. Use the classic throw form in the Opc.Ua.Client.TestFramework RequireSubscriptionManager helper, matching the same fix already applied to ManagedSessionExtensions.
Extend ClassicEngineDoesNotExposeSubscriptionManager to assert that the V2-only fluent surfaces (AddSubscription, DefaultStreaming) throw InvalidOperationException on a classic-engine ManagedSession, exercising the new guard branches in ManagedSessionExtensions.GetSubscriptionManager and ManagedSession.DefaultStreaming.
Add an internal session-injecting ServerSession constructor (testability seam) and ServerSessionCapabilityGuardTests, which bind a Mock<ISession> whose TryGetSubscriptionManager returns false to exercise the classic-engine guards: CreateDataChangeSubscriptionAsync throws BadNotSupported and StartModelChangeMonitoringAsync no-ops. These branches are unreachable through the adapter's normal V2-session path, so the seam is required to cover them.
Brings in 2 upstream commits: OPCFoundation#3933 (promote V2 SubscriptionManager to ISession.TryGetSubscriptionManager) and OPCFoundation#3932 (honor SecurityConfiguration in GDS push UpdateCertificate validation). No textual merge conflicts, but OPCFoundation#3933 replaced the ManagedSession.SubscriptionManager property (which threw InvalidOperationException on the classic engine) with a bool ISession.TryGetSubscriptionManager(out ISubscriptionManager?) try-get. Adapted the 9 UaLens call sites: * ChannelV2EngineAdapter.cs — the six worker-pool / republish counter getters (PublishWorkerCount, BadPublishRequestCount, MissingMessageCount, RepublishMessageCount, MinPublishWorkerCount, MaxPublishWorkerCount) now use the try-get ternary (`TryGetSubscriptionManager(out var mgr) ? mgr.X : 0`) instead of try/catch(InvalidOperationException); the subscription-create path throws an explicit InvalidOperationException when the V2 manager is unavailable, preserving prior behavior. * SubscriptionBenchPlugin.cs — the bench's Add path now checks TryGetSubscriptionManager up front (V2-engine-required guard) before calling Add; the engine-metrics panel switched its try/catch(InvalidOperationException) fallback to a plain if/else on the try-get result. UaLens build clean (0 warnings / 0 errors). Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Follow-up to the merged PR #3892 implementing issue #3925.
Summary
Promotes the V2 options-based subscription manager onto
ISessionso the PubSub external-server adapter no longer downcasts to the concreteManagedSession.The V2
ISubscriptionManageris backed by the session's subscription engine, so it is a Session capability rather than a managed-only one. It is now exposed onISessionvia a non-throwing accessor:Returns
true+ the manager for V2-engine sessions (the default forManagedSession) andfalsefor classic-engine sessions (the default for the unmanagedSession).Changes
ISession: addTryGetSubscriptionManager.Session(unmanaged): implement off the active subscription engine.ManagedSession: implement by delegating to the inner session. The throwingManagedSession.SubscriptionManagerproperty is removed (source-breaking, 2.0 preview) in favour of theISessionaccessor.ManagedSessionExtensionshelpers (signatures unchanged, via a private resolver),ManagedSession.Streaming, and the client subscription/session/stress test suites (via aRequireSubscriptionManagertest helper).ServerSessionreaches the subscription manager throughTryGetSubscriptionManagerandMessageContextthroughISessiondirectly (it was already onISessionviaIClientBase). All three(ManagedSession)downcasts and theCA1859suppression are removed;CreateSessionAsyncreturnsISession. An internal session-injecting constructor was added as a test seam for the classic-engine capability guards.Sessions.md/Subscriptions.mdupdated;migrate/2.0.x/sessions-subscriptions.mddocuments the property removal.Review feedback addressed
BadNotSupportedmessage (names the V2 engine + endpoint); classic-engine model-change monitoring log clarified as optional/best-effort.GetSubscriptionManagerargument null-check.SubscriptionManagertype before downcasting.CI fixes
ArgumentNullException.ThrowIfNull(unavailable on net472/net48/netstandard2.0) with the classic throw form in the test helper.AddSubscription/DefaultStreamingthrows;ServerSessiondata-change throw + model-change no-op) to keep PR diff coverage above target.Validation
Opc.Ua.Client+Opc.Ua.PubSub.Adapterbuild 0-warning on net10 and net48.Opc.Ua.Subscriptions.TestsandOpc.Ua.PubSub.Adapter.Testspass; no(ManagedSession)casts remain in product code.Closes #3925