Skip to content
Merged
Show file tree
Hide file tree
Changes from 1 commit
Commits
Show all changes
51 commits
Select commit Hold shift + click to select a range
36d67b3
[Client] Central channel manager with shared, ref-counted transport c…
marcschier Jun 5, 2026
82347dc
Address PR #3852 review feedback
marcschier Jun 5, 2026
0a32452
Merge branch 'master' into channelrefinements
marcschier Jun 5, 2026
06fbbb5
Add channel-manager stress/chaos test suite (Tests/Opc.Ua.Channels.St…
marcschier Jun 5, 2026
3a5477b
Fix CI failures: HTTPS factory regression + parallel build file lock
marcschier Jun 6, 2026
81e3d4b
Fix CI follow-ups: serialize project build + relax cert leak tolerance
marcschier Jun 6, 2026
0b61c6d
Fix Sessions failover tests masked by budget-aware delegate
marcschier Jun 6, 2026
48b24be
Fix CodeQL whole-solution build failures across legacy TFMs
marcschier Jun 6, 2026
d5f1dbb
Restore CustomTestTarget passthrough for Channels.Stress.Tests
marcschier Jun 6, 2026
cf6bd37
Address PR feedback: small-scope cleanup
marcschier Jun 6, 2026
2efd335
Phase C: move ClientChannelManagerManagedTests out of Core.Tests
marcschier Jun 6, 2026
35f9749
Phase A+B: merge stress test projects + rename workflow
marcschier Jun 6, 2026
193fd4e
Phase D: refactor ClientChannelManager to single non-partial class + …
marcschier Jun 6, 2026
ff33cdd
Phase E: auto-reset faulted ChannelEntry on ReconnectAsync(lease, ...)
marcschier Jun 6, 2026
788567a
Merge branch 'master' of https://github.com/OPCFoundation/UA-.NETStan…
marcschier Jun 8, 2026
de83b91
update
marcschier Jun 8, 2026
b1ba2cc
Merge branch 'master' into channelrefinements
marcschier Jun 8, 2026
961329e
Merge branch 'channelrefinements' of https://github.com/OPCFoundation…
marcschier Jun 8, 2026
c23f477
Merge branch 'master' into channelrefinements
marcschier Jun 8, 2026
81acd94
Close channel-manager production gaps and eliminate Tests/.../Gaps/
marcschier Jun 8, 2026
ce4a1b0
Fix ClientChannelManager.DelaySwapAsync to honor TimeProvider on net48
marcschier Jun 9, 2026
8116f79
Fix ChannelManagerExhaustionEscalatesAndRecoversWhenServerReturns mac…
marcschier Jun 9, 2026
51e7f42
Fix SwapFaultedEntryAsync: re-activate released lease before reattach
marcschier Jun 9, 2026
53ff029
Fix MetricsAreEmittedForReconnectAndGateWaitAsync race on fast runners
marcschier Jun 10, 2026
392ad0d
Merge origin/master into channelrefinements
marcschier Jun 10, 2026
9c5f875
Honor ShutdownToken in ChannelEntry reconnect loop (PR #3852 feedback)
marcschier Jun 10, 2026
5ca95ab
[security] HttpsTransportChannel: refuse factory path when OPC UA cer…
marcschier Jun 11, 2026
3f4db2a
[security] stress-test.yml: env-var indirection for workflow_dispatch…
marcschier Jun 11, 2026
30495fb
[security] buildandtest.yml: add workflow-scoped permissions: content…
marcschier Jun 11, 2026
03df798
[security] ClientChannelManagerDiagnostics: do not leak inner-excepti…
marcschier Jun 11, 2026
39eb086
ChannelEntry: track tightest IRetryBudget across coalesced joiners; C…
marcschier Jun 11, 2026
2a4f4f6
ManagedTransportChannelLease: non-blocking sync Dispose (deadlock und…
marcschier Jun 11, 2026
d0ef235
RetryBudget: swap Reset() write order to avoid stale-tick race
marcschier Jun 11, 2026
b50ffa0
[security] stability-test.yml: read TEST_DURATION_MINUTES as a shell …
marcschier Jun 11, 2026
38d4a29
[security] McpServer NodeSetExportTools: constrain MCP-tool file path…
marcschier Jun 11, 2026
2be0f5c
[docs] Document the secure-by-default DI HttpClient fallback and the …
marcschier Jun 11, 2026
25dc407
[security] ClientChannelManagerCertRotation: refuse rotation events t…
marcschier Jun 11, 2026
04f3d12
[security] Bound the participant metric tag to per-kind cardinality (…
marcschier Jun 11, 2026
a3ae6db
[test] Accommodate non-blocking lease Dispose with thread-safe listen…
marcschier Jun 11, 2026
5353c69
Merge branch 'master' into channelrefinements
marcschier Jun 11, 2026
22828d9
[ci] Stress.Tests: produce a placeholder assembly for net48/472/netst…
marcschier Jun 12, 2026
5c68a6b
Add OPC UA packet capture, offline decoding and replay (Opc.Ua.Bindin…
marcschier Jun 12, 2026
870bd0d
Merge origin/master into channelrefinements
marcschier Jun 12, 2026
9d500da
[ci] Fix compile errors in upstream-merged test files
marcschier Jun 12, 2026
6c9ce69
[ci] Fix ChannelManagerMaxChannelsTests using-alias for nested IChannel
marcschier Jun 12, 2026
b820352
[ci] Fix Pcap test failures: 5 production / test-infra bugs
marcschier Jun 12, 2026
5cc4860
[ci] Fix 3 Pcap test cross-platform / timing issues
marcschier Jun 12, 2026
f691872
Merge branch 'master' into channelrefinements
marcschier Jun 13, 2026
6382a8c
[ci] Drop duplicate Microsoft.Extensions.TimeProvider.Testing Package…
marcschier Jun 13, 2026
862cd36
[ci] Gate Opc.Ua.Network.Fuzz* projects on CustomTestTarget like Pcap
marcschier Jun 13, 2026
7e37ab1
[test] Poll channel.State for Ready after channel-manager swap
marcschier Jun 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions Applications/ConsoleReferenceClient/ConnectTester.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,9 @@
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/

// CS0618: legacy SessionReconnectHandler retained for back-compat demo
Comment thread
marcschier marked this conversation as resolved.
Outdated
#pragma warning disable CS0618

using System;
using System.Collections.Generic;
using System.IO;
Expand Down
7 changes: 6 additions & 1 deletion Applications/ConsoleReferenceClient/UAClient.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,11 @@
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/

// CS0618: this sample retains SessionReconnectHandler for back-compat
// demonstration; new code should use ManagedSession with channel manager.
#pragma warning disable CS0618

using System;
using System.Collections;
Comment thread
marcschier marked this conversation as resolved.
using System.Threading;
using System.Threading.Tasks;
using Microsoft.Extensions.Logging;
Expand All @@ -50,7 +53,7 @@
public UAClient(
ApplicationConfiguration configuration,
ITelemetryContext telemetry,
Action<IList, IList>? validateResponse)

Check failure on line 56 in Applications/ConsoleReferenceClient/UAClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type or namespace name 'IList' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 56 in Applications/ConsoleReferenceClient/UAClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type or namespace name 'IList' could not be found (are you missing a using directive or an assembly reference?)
Comment thread
marcschier marked this conversation as resolved.
Outdated
: this(
configuration,
null,
Expand All @@ -66,7 +69,7 @@
ApplicationConfiguration configuration,
ReverseConnectManager? reverseConnectManager,
ITelemetryContext telemetry,
Action<IList, IList>? validateResponse)

Check failure on line 72 in Applications/ConsoleReferenceClient/UAClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type or namespace name 'IList' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 72 in Applications/ConsoleReferenceClient/UAClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type or namespace name 'IList' could not be found (are you missing a using directive or an assembly reference?)
{
ValidateResponse = validateResponse;
m_logger = telemetry.CreateLogger<UAClient>();
Expand Down Expand Up @@ -107,7 +110,7 @@
/// <summary>
/// Action used
/// </summary>
internal Action<IList, IList>? ValidateResponse { get; }

Check failure on line 113 in Applications/ConsoleReferenceClient/UAClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type or namespace name 'IList' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 113 in Applications/ConsoleReferenceClient/UAClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type or namespace name 'IList' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 113 in Applications/ConsoleReferenceClient/UAClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type or namespace name 'IList' could not be found (are you missing a using directive or an assembly reference?)

Check failure on line 113 in Applications/ConsoleReferenceClient/UAClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

The type or namespace name 'IList' could not be found (are you missing a using directive or an assembly reference?)

/// <summary>
/// Gets the client session.
Expand Down Expand Up @@ -356,7 +359,9 @@
{
// detach the channel, so it doesn't get
// closed when the session is disposed.
#pragma warning disable CS0618 // sample legacy path
Session.DetachChannel();
#pragma warning restore CS0618
}
Session.Dispose();
// Transiently null after dispose; matches pre-NRT behavior.
Expand Down Expand Up @@ -514,7 +519,7 @@
private readonly Lock m_lock = new();
private readonly ReverseConnectManager? m_reverseConnectManager;
private readonly ApplicationConfiguration m_configuration;
private SessionReconnectHandler? m_reconnectHandler;

Check failure on line 522 in Applications/ConsoleReferenceClient/UAClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

'SessionReconnectHandler' is obsolete: 'Use ManagedSession (with optional WithChannelManager) for managed reconnect. Channel-level reconnect is now coordinated centrally via IClientChannelManager. SessionReconnectHandler remains functional for back-compat with existing callers that drive a raw Session directly.'

Check failure on line 522 in Applications/ConsoleReferenceClient/UAClient.cs

View workflow job for this annotation

GitHub Actions / Analyze (csharp)

'SessionReconnectHandler' is obsolete: 'Use ManagedSession (with optional WithChannelManager) for managed reconnect. Channel-level reconnect is now coordinated centrally via IClientChannelManager. SessionReconnectHandler remains functional for back-compat with existing callers that drive a raw Session directly.'
private readonly ILogger m_logger;
private readonly ITelemetryContext m_telemetry;
private bool m_disposed;
Expand Down
5 changes: 5 additions & 0 deletions Applications/McpServer/OpcUaSessionManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,11 @@
* http://opcfoundation.org/License/MIT/1.00/
* ======================================================================*/

// CS0618: legacy SessionReconnectHandler is intentionally retained in this
Comment thread
marcschier marked this conversation as resolved.
Outdated
// sample for now; replacing it with ManagedSession / channel manager is a
// follow-up. Suppress at file scope.
#pragma warning disable CS0618

using System;
using System.Collections.Concurrent;
using System.Collections.Generic;
Expand Down
2 changes: 2 additions & 0 deletions Directory.Packages.props
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,8 @@
<PackageVersion Include="Microsoft.Extensions.Logging.Console" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.DependencyInjection.Abstractions" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Diagnostics" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Http" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Http.Resilience" Version="10.6.0" />
<PackageVersion Include="Microsoft.Extensions.Hosting" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Hosting.Abstractions" Version="10.0.8" />
<PackageVersion Include="Microsoft.Extensions.Options" Version="10.0.8" />
Expand Down
33 changes: 33 additions & 0 deletions Docs/DependencyInjection.md
Original file line number Diff line number Diff line change
Expand Up @@ -557,6 +557,39 @@ when the server initiates the session. The
`ReverseConnect` configuration is also consumed by `Session` /
`SessionReconnectHandler` for reconnect-via-reverse-hello scenarios.

## Channel manager

`AddClient(...)` automatically registers an `IClientChannelManager`
singleton (`ClientChannelManager` implementation) built from
`OpcUaClientOptions.Configuration`. The
`Func<CancellationToken, Task<ManagedSession>>` delegate wires this
manager into every new `ManagedSession` it creates, so multiple
`ManagedSession` instances resolved from the same DI container will
share underlying transport channels per
`ConfiguredEndpoint`. Reconnect is coalesced and notified to attached
sessions transparently. On .NET 8 and later, HTTPS transport channels
also get the named `IHttpClientFactory` client (`Opc.Ua.Client`) and its
standard resilience handler through `AddClient(...)`. See
[Sessions and reconnect § 4](Sessions.md#4-iclientchannelmanager--centralised-channel-sharing-and-reconnect)
for details on identity, retry policy, HTTPS request resilience, and the
shared retry budget between channel-manager reconnect and
`ManagedSession`'s outer `IReconnectPolicy`.

To override the default channel manager (e.g. to inject a custom
`IChannelReconnectPolicy`):

```csharp
services.AddOpcUa().AddClient(opt => { ... });
services.Replace(ServiceDescriptor.Singleton<IClientChannelManager>(sp =>
new ClientChannelManager(
sp.GetRequiredService<OpcUaClientOptions>().Configuration!,
sp.GetRequiredService<ITelemetryContext>(),
reconnectPolicy: new ExponentialBackoffChannelReconnectPolicy
{
MaxAttempts = 5
})));
```

## Application instance (advanced)

```csharp
Expand Down
60 changes: 58 additions & 2 deletions Docs/MigrationGuide.md
Original file line number Diff line number Diff line change
Expand Up @@ -1386,10 +1386,65 @@ Version 2.0 introduces `ManagedSession`, a wrapper around `Session` that automat

- **`ManagedSessionFactory`** is a **new** factory that creates `ManagedSession` instances which handle reconnection and failover automatically. Use this when you want managed-session behavior.
- **`DefaultSessionFactory`** is **unchanged** — it continues to create raw `Session` instances. Existing code that constructs `DefaultSessionFactory` directly keeps the same behavior in 2.0.
- **`SessionReconnectHandler`** is **retained** as a supported legacy entry point for callers that already manage raw `Session` instances. The type itself is not removed. Its parameterless legacy constructor remains marked `[Obsolete("Use SessionReconnectHandler(ITelemetryContext, bool, int) instead.")]` in 2.0 (the same attribute was already present in 1.5.378); pass an `ITelemetryContext` to the new ctor when adopting it. It now also requires the wrapped `ISession` to be a `Session` (or a derived type) — passing a `ManagedSession` (or any other `ISession` facade) throws `NotSupportedException`, since those facades drive their own reconnect / failover state machine. New code should still prefer `ManagedSessionFactory` / `ManagedSession.CreateAsync`.
- **`SessionReconnectHandler`** is now marked **`[Obsolete]`** as of issue [#3288](https://github.com/OPCFoundation/UA-.NETStandard/issues/3288). It remains functional for back-compat with existing callers that own raw `Session` instances. The type itself is not removed. Its parameterless legacy constructor remains marked `[Obsolete("Use SessionReconnectHandler(ITelemetryContext, bool, int) instead.")]`; pass an `ITelemetryContext` to the new ctor when adopting it. It now also requires the wrapped `ISession` to be a `Session` (or a derived type) — passing a `ManagedSession` (or any other `ISession` facade) throws `NotSupportedException`. New code should use `ManagedSessionFactory` / `ManagedSession.CreateAsync` (which transparently uses the new `IClientChannelManager` if registered), or wire `IClientChannelManager` into a raw `Session` via `Session.CreateAsync(IClientChannelManager, ...)`.
Comment thread
marcschier marked this conversation as resolved.
Outdated

- **`IClientBase.AttachChannel(ITransportChannel)` and `DetachChannel()`** are now marked **`[Obsolete]`** as of issue [#3288](https://github.com/OPCFoundation/UA-.NETStandard/issues/3288). They remain functional. New code should use `IClientChannelManager` so transport channels are reference-counted, shared across participants on the same endpoint, and reconnected transparently. See [Sessions and reconnect](Sessions.md#4-iclientchannelmanager--centralised-channel-sharing-and-reconnect).

- **`Session.ReconnectAsync(ITransportWaitingConnection, ITransportChannel, CancellationToken)`** is the underlying instance method; it is *not* marked obsolete. The two convenience extension overloads `ReconnectAsync(connection, ct)` and `ReconnectAsync(channel, ct)` (in `SessionExtensions`) **are** now `[Obsolete]` because they bypass the central channel manager. The simple `ReconnectAsync(ct)` extension is unchanged and routes through the channel manager automatically when one is wired.

For a deeper architectural picture of how `Session`, `ManagedSession`, `SessionReconnectHandler`, and the subscription engines fit together, see [Sessions, Reconnection, and Subscription Engines](Sessions.md).

#### Shared reconnect budget for ManagedSession and the channel manager
Comment thread
marcschier marked this conversation as resolved.
Outdated

`ManagedSession` and `IClientChannelManager` now share one retry deadline for a reconnect cycle instead of
stacking independent session-level and channel-level budgets. See
[Sessions and reconnect § 4](Sessions.md#shared-retry-budget-with-managedsession) for the runtime sequence and a
worked example.

- **`IRetryBudget` and `RetryBudget`** are new core types. `RetryBudget` is a thread-safe implementation backed by
`TimeProvider`; it starts timing on the first consumed attempt, reports the remaining time via `TryConsume`, and
resets after a successful reconnect so the next reconnect cycle receives a fresh budget.
- **`IClientChannelManager.ReconnectAsync(channel, IRetryBudget, ct)`** lets the outer `ManagedSession` retry driver
pass its deadline into the channel manager. The existing two-argument `ReconnectAsync(channel, ct)` overload still
works and uses an unlimited budget for back-compat. On TFMs where the interface does not expose the new overload,
the built-in `ClientChannelManager` still accepts it directly; custom managers fall back to the legacy overload.
- **`IChannelReconnectPolicy.GetDelay(int, IRetryBudget?)`** is available as a default interface method on the TFMs
where the stack exposes it. The default delegates to the existing `GetDelay(int)` and clamps the result to the
remaining budget. On `net48`, custom policies that need custom budget-aware behavior should implement
`IBudgetAwareChannelReconnectPolicy`; otherwise the manager calls the legacy method and applies the same budget
clamp around it.

To opt in, set `ReconnectPolicyOptions.MaxTotalReconnectTime` to the end-to-end reconnect window. The default is five
minutes.

```csharp
ManagedSession session = await new ManagedSessionBuilder(configuration, telemetry)
.UseEndpoint(endpoint)
.WithReconnectPolicy(p => p with
{
MaxTotalReconnectTime = TimeSpan.FromSeconds(30)
})
.ConnectAsync(ct);
```

For DI configuration, set the same option on `ManagedSessionOptions.ReconnectPolicy`:

```csharp
services.AddOpcUa()
.AddClient(opt =>
{
opt.Configuration = config;
opt.Session = opt.Session with
{
Endpoint = endpoint,
ReconnectPolicy = opt.Session.ReconnectPolicy with
{
MaxTotalReconnectTime = TimeSpan.FromSeconds(30)
}
};
});
```

**Migration**:

**If you use `DefaultSessionFactory`:**
Expand Down Expand Up @@ -1459,7 +1514,8 @@ var policy = new ReconnectPolicy
InitialDelay = TimeSpan.FromSeconds(1),
MaxDelay = TimeSpan.FromSeconds(30),
MaxRetries = 0, // 0 = unlimited
JitterFactor = 0.1 // ±10% jitter
JitterFactor = 0.1, // ±10% jitter
MaxTotalReconnectTime = TimeSpan.FromMinutes(5)
};
```

Expand Down
Loading
Loading