Commit Graph
4 Commits
Author SHA1 Message Date
Marc Schier 8d2402c995 Bump version to 2.0 and land 1.5.378 to 2.0 migration guide (#3819)
Prepares the repository for a 2.0 release: bumps every version reference
from 1.6 to 2.0, and lands the migration guide describing the 1.5.378 →
2.0 upgrade path. The 1.6 line never shipped, so this is a straight
rename with no historical 1.6 migration content to preserve.

The PR is split into two commits:

1. **`Bump version to 2.0 (next release is a major version change)`** —
version metadata, NuGet metadata, the side documentation, and the
migration agent file.
2. **`[Docs] Close 19 audit gaps in 1.5.378 to 2.0 migration guide`** —
adds and reorganizes `Docs/MigrationGuide.md` to close every gap from a
rigorous code-review audit of the 1.5.378 → 2.0 breaking-change surface.

### Version bump (commit 1)

- `version.json`: `1.6-preview` → `2.0-preview`
- `common.props`: `<VersionPrefix>1.6</VersionPrefix>` →
`<VersionPrefix>2.0</VersionPrefix>`
- `nuget/Opc.Ua.nuspec` and `nuget/Opc.Ua.Symbols.nuspec`: `1.6.0` →
`2.0.0`
- `README.md`: "New in 1.6" → "New in 2.0"
- `Docs/ComplexTypes.md`, `Docs/Observability.md`, `Docs/Sessions.md`:
1.6 → 2.0 mentions
- Renames `.github/agents/opcua-v16-migration.agent.md` →
`.github/agents/opcua-v20-migration.agent.md` and updates the agent
metadata (name, description, trigger phrases, version table)
- `UA.slnx`: solution-items reference updated to the new agent filename
-
`Tools/Opc.Ua.SourceGeneration.Core/Generators/ObjectTypeProxyGenerator.cs`:
two v16 → v20 comment fixes

### Migration guide (commit 2)

Closes 19 gaps from an audit comparing `master378` (1.5.378) with
`master` (2.0). Adds a new top-level **Package, Target Framework and
Dependency Changes** section covering the new `Opc.Ua.Core.Types` and
`Opc.Ua.Security.Certificates` packages, the `LibTypesTargetFrameworks`
netstandard2.0 drop, the NuGet dependency additions/removals, and the
residual Newtonsoft.Json dependency in `Opc.Ua.PubSub`.

In-place additions:

- new H4 for `INodeManager3` under Node States
- `Session.Call` `Variant.From` worked example
- new H3 `Subscriptions and Transports` with H4 verdicts for Durable
subscriptions (source-breaking), PubSub (no source break) and Reverse
connect (no source break)
- new H3 `Telemetry and Logging` covering `ITelemetryContext`
constructor-injection patterns
- `in DataValue` async copy-to-local pattern
- UNSAFE / SAFE pair for `WithPoolNotifications` pooling
- using-alias example for classic vs V2 `SubscriptionOptions`
- "Silent regression" callout for `OnAfterCreate`
- numbered lifecycle ordering for
`ApplicationConfiguration.CertificateManager`

Corrections:

- clarifies that the `SessionReconnectHandler` `[Obsolete]` is still
present in 2.0 (not withdrawn);
- clarifies that the source-generated `ServerCapability` constants vs
the `ServerCapabilityInfo` registry type are distinct;
- disambiguates `ReconnectPolicyOptions` (DTO) from `ReconnectPolicy`
(runtime).

Adds an "Automate the migration" pointer to the forthcoming
`Opc.Ua.CodeFixers` analyzer package, which mechanizes 17 of the
patterns covered in this guide. The analyzer is published in a follow-up
PR.
2026-05-29 08:18:41 +02:00
Marc Schier f3928db05b Add unified Microsoft.Extensions DI surface across OPC UA libraries (#3803)
Introduces a consistent `Microsoft.Extensions.DependencyInjection`
surface across the OPC UA libraries. Every consumer starts from a single
root entry point and chains feature-specific extension methods on a
unified builder. PubSub is intentionally excluded per the original
scope.

```csharp
services
    .AddOpcUa()                          // Stack/Opc.Ua.Core
    .AddServer(o => { /* … */ })         // Libraries/Opc.Ua.Server
    .AddNodeManager<MyNodeManagerFactory>()
    .Services
    .AddOpcUa()
    .AddClient(o => { /* … */ });        // Libraries/Opc.Ua.Client
```

Manual non-DI constructors (`new ApplicationInstance(...)`, `new
StandardServer(...)`, `new LdsServer(...)`, `new ManagedSession(...)`,
etc.) remain unchanged — DI is purely additive for users who want the
.NET Generic Host to own application lifetime, logging, and
configuration.

### What ships

| Feature library | Method on `IOpcUaBuilder` | Returns |

|----------------------------------------------|-------------------------------------------|--------------------------|
| `Opc.Ua.Core` (root) | `services.AddOpcUa()` | `IOpcUaBuilder` |
| `Opc.Ua.Configuration` | `builder.AddApplicationInstance()` |
`IOpcUaBuilder` |
| `Opc.Ua.Client` | `builder.AddClient(opt => …)` |
`IOpcUaClientBuilder` |
| `Opc.Ua.Client.ComplexTypes` | `builder.AddComplexTypes()` |
`IOpcUaBuilder` |
| `Opc.Ua.Server` | `builder.AddServer(opt => …)` |
`IOpcUaServerBuilder` |
| `Opc.Ua.Gds.Client.Common` | `builder.AddGdsClient(opt => …)` |
`IGdsClientBuilder` |
| `Opc.Ua.Gds.Server.Common` | `builder.AddGdsServer(opt => …)` |
`IGdsServerBuilder` |
| `Opc.Ua.Lds.Server` | `builder.AddLdsServer(opt => …)` |
`ILdsServerBuilder` |
| `Opc.Ua.WotCon.Server` | `builder.AddWotConServer(opt => …)` |
`IWotConServerBuilder` |
| `Opc.Ua.WotCon.Client` | `builder.AddWotConClient(opt => …)` |
`IOpcUaBuilder` |

### Design highlights

- **Binary-safe inheritance** — `IOpcUaBuilder :
IDependencyInjectionBuilder` so any existing `AddLogging(this
IDependencyInjectionBuilder)` callers keep compiling unchanged.
- **Three options overloads** per feature: `Action<TOptions>`
(AOT-safe), `IConfiguration`, `IConfigurationSection`. The two binding
overloads are annotated `[RequiresUnreferencedCode]` /
`[RequiresDynamicCode]`. Section names follow the `OpcUa:<Feature>`
convention (`OpcUa:Server`, `OpcUa:Client`, `OpcUa:Gds:Server`,
`OpcUa:Lds`, …).
- **Generic Host integration** — each server feature (regular, GDS, LDS)
registers its own `BackgroundService`. Marker singletons throw
`InvalidOperationException` on duplicate `.AddServer()` /
`.AddGdsServer()` / `.AddLdsServer()`. A regular server + GDS + LDS can
coexist in one host; each owns its own `IApplicationInstance` produced
by a shared `IApplicationInstanceFactory` so PKI roots, endpoints, and
certificates don't collide.
- **Registration isolation** — node managers registered under the
regular server via `.AddNodeManager<T>()` are wrapped in
`OpcUaServerNodeManagerRegistration` so they don't bleed into the GDS /
LDS hosted services.
- **WoT Connectivity server** composes on top of the regular server
feature: `.AddServer(...).AddWotConServer(...)` registers
`WotConnectivityNodeManagerFactory` as a server node-manager
registration.
- **Unified telemetry** — `services.AddOpcUa()` registers
`ITelemetryContext` via `TryAddSingleton` using a new
`ServiceProviderTelemetryContext` that resolves the host's
`ILoggerFactory` on first use. User-supplied registrations win.

### Removed pre-release DI surface

The pre-release `services.AddOpcUaServer(...)`,
`services.AddOpcUaClient(...)`, `services.AddOpcUaGdsClient(...)`
extensions are deleted. `IClientBuilder` was renamed to
`IOpcUaClientBuilder`. In-repo callers migrated:

- `Applications/MinimalCalcServer/Program.cs`
- `Applications/MinimalBoilerServer/Program.cs`
- `Tests/Opc.Ua.Client.Tests/ClientBuilder/OpcUaClientBuilderTests.cs`
(renamed from `OpcUaClientServiceCollectionExtensionsTests.cs`)
- Docs (`MigrationGuide.md`, `Sessions.md`,
`SourceGeneratedNodeManagers.md`, README, new
`Docs/DependencyInjection.md`)

### csproj audit

Pruned every redundant `Microsoft.Extensions.*` package reference.
`Opc.Ua.Core` already pulls in `Microsoft.Extensions.Diagnostics` and
`Microsoft.Extensions.Logging`, which transitively expose DI / Options /
Configuration.Abstractions / Options.ConfigurationExtensions to every
downstream library. Only `Microsoft.Extensions.Hosting.Abstractions`
stays explicit on the server libraries (it isn't transitively
available). Also pruned redundant `System.Formats.Asn1` (Bindings.Https)
and `System.Collections.Immutable` (Security.Certificates).

### Tests

50 new tests in eight projects:

| Project                              | Tests |
|--------------------------------------|-------|
| `Opc.Ua.Core.Tests`                  | 12    |
| `Opc.Ua.Client.Tests`                | 4     |
| `Opc.Ua.Client.ComplexTypes.Tests`   | 3     |
| `Opc.Ua.WotCon.Tests`                | 6     |
| `Opc.Ua.Lds.Tests`                   | 5     |
| `Opc.Ua.Gds.Tests`                   | 6     |
| `Opc.Ua.Server.Tests` (integration)  | 10    |
| `Opc.Ua.Aot.Tests` (smoke)           | 4     |
| **Total**                            | **50** |

Combined-host integration tests cover the rubber-duck-driven scenarios:
telemetry-context override across legacy and new entry paths, server +
GDS + LDS coexistence with distinct ports and PKI roots,
duplicate-registration rejection, and node-manager isolation across
features.

### AOT

`dotnet publish Tests/Opc.Ua.Aot.Tests -c Release -r win-x64
--self-contained` reports zero `IL2026` / `IL3050` / `IL2104` warnings
from the new DI surface. All generic registrations carry
`[DynamicallyAccessedMembers(PublicConstructors)]`. AOT consumers stick
to the `Action<TOptions>` overload exclusively; the new
`Tests/Opc.Ua.Aot.Tests/HostingAotTests.cs` smoke fixture exercises this
end-to-end.
2026-05-25 20:24:53 +02:00
Marc Schier 239a40b183 GC reduction via IPooledEncodeable activator pooling (#3793)
* GC reduction via IPooledEncodeable activator pooling

Add opt-in activator-level pooling for IEncodeable instances decoded
during V2 subscription publish dispatch. The design replaces the
earlier IAllocator-on-IServiceMessageContext approach (which had a
cross-contamination hazard for non-publish decodes) with a clean
factory-based model where pooling lives on the encodeable activator.

New public API:
- IPooledEncodeable : IEncodeable - opt-in marker with Reuse() method
- PooledEncodeableType<T> : EncodeableType<T> - activator base with
  BoundedObjectPool<T> (lock-free, no new package dependency)
- ISubscriptionManager.PoolNotifications { get; set; }
- ManagedSessionOptions.PoolNotifications { get; init; }
- ManagedSessionBuilder.WithPoolNotifications(bool)

Source-gen:
- All concrete non-abstract structure types (including service
  request/response types) are generated with PooledEncodeableType<T>
  activators and IPooledEncodeable.Reuse() bodies by default
- Derived types use 'new' to hide inherited Reuse/ClearPooledSentinel
  so each concrete type routes through its own activator's pool
- No changes to external design XML files (UA Core Services.xml etc.)
  - poolable selection is hardcoded in the generator

Benchmarks (MonitoredItemNotification, 1000 ops):
- Allocations: 128 KB -> 408 B (~315x reduction)
- Gen0 collections: 29.75 -> 0.09 per 1000 ops

No interface changes to IEncodeable, IDecoder, IServiceMessageContext,
or ISubscriptionNotificationHandler. Handlers retain by copy when
pooling is enabled.
Call IPooledEncodeable.Reuse() on decoded IServiceRequest and
IServiceResponse after the server is done with them:

- TcpTransportListener.OnRequestReceivedAsync: reuse both request
  and response in a finally block after ProcessRequestAsync +
  SendResponse complete
- EndpointBase.InvokeServiceAsync (HTTPS path): same pattern after
  ProcessRequestAsync + BinaryEncoder.EncodeMessage

Walk acknowledged, overflow-evicted, and disposed NotificationMessage
objects and call IPooledEncodeable.Reuse() on their notification
payloads (MonitoredItemNotification, DataChangeNotification,
EventFieldList, EventNotificationList).

Insertion points in Subscription.cs:
- Acknowledge(): after RemoveAt from m_sentMessages
- InnerPublish() overflow: before RemoveRange/Clear on m_sentMessages
- Dispose(): before m_sentMessages.Clear()
- InnerPublish() unsent-message overflow: before RemoveRange on
  the messages list

Replace new T() with XxxActivator.Instance.CreateInstance() for all
server-side notification payload types so instances are rented from
the activator pool instead of heap-allocated:

New static helper ReuseNotificationPayloads() walks the
NotificationMessage.NotificationData, extracts DataChangeNotification
and EventNotificationList via TryGetValue, iterates their items, and
calls Reuse() on each pooled payload.
2026-05-22 12:42:33 +02:00
Marc Schier bd4543e961 ManagedSession, Pluggable Subscription engine, and fluent builder + DI (#3730)
- Add new managed session, wrapping session and providing connection management and failover handling
- Add new Subscription engine that can act as alternative to current engine, but is based on channels with worker tasks
- Add a fluent builder for managed session clients
- Replace NodeCache with LruNodeCache, rename ILruNodeCache and LruNodeCache to INodeCache and NodeCache
- Update migration guide and document new functionality.
2026-05-05 19:09:07 +02:00