1 Commits
Author SHA1 Message Date
romanettandClaude Opus 5.5 0716603543 Fix concurrent session creation on a shared ClientChannelManager channel (#4739)
# Description

Sessions created concurrently through one `ClientChannelManager` against
the same endpoint (`Session.CreateAsync(IClientChannelManager, ...)`)
failed with `BadSecureChannelClosed 'The managed transport is closed.'`,
and the log showed `Background operation OpenChannel started by
ClientChannelManager failed. Channel is Closed.` Creating the same
sessions one after another worked.

## Root cause

A caller that finds an existing shared entry gets a lease right away,
even while the entry's creator is still opening the transport:

1. That session's `OpenAsync` called
`lease.Entry.SnapshotClientCertificate()` before any transport was
installed and threw *"The managed transport is closed."*
2. The failed session disposed its lease. The refcount dropped to 0 and
the entry was torn down.
3. The creator's in-flight `OpenInitialAsync` then found the entry
closing and threw *"Channel is Closed."* Every session attached to that
entry failed.

## Changes

- **`Session.OpenAsync`** waits until the shared entry has an installed
transport (new `ChannelEntry.WaitForTransportAsync`) before it reads the
certificate. The signal fires when the initial open installs a
transport, or when a reconnect that started during the initial open
does. It does not wait for `Ready`: `OpenCoreAsync` also runs for
session recreate inside a reconnect cycle, and the cycle installs the
transport before participants reactivate.
- **`ClientChannelManager.GetCoreAsync`**: the creator holds no lease
while it opens the entry, so other participants' releases can close the
entry under the open. The creator now retries on a fresh entry instead
of failing. When the initial open fails on its own, the entry records it
(`InitialOpenFaulted`, set under the entry lock at failure time, because
the later lifecycle state cannot tell the two cases apart). A faulted
open, or one closed by manager disposal, still fails the creator.

## Tests

-
`SharedChannelConcurrentSessionTests.SessionAttachedWhileChannelOpensWaitsForTransportAsync`
(Client.Tests): holds the creator's transport through gated channel
bindings until a second session has attached to the opening entry, then
checks that both sessions open and share the channel. Fails
deterministically without the fix.
-
`ClientChannelManagerManagedTests.WaitForTransportWaitsForReconnectStartedDuringInitialOpenAsync`
and `CreatorDoesNotRetryInitialOpenThatFaultedAsync` (Core.Tests): cover
the reconnect-during-open wait and the no-retry-on-fault path.
-
`SharedChannelConcurrentSessionTests.ConcurrentlyCreatedSessionsShareOneChannelAsync`
(Client.Tests): creates 20 sessions concurrently through one manager
against the reference server. It checks that they share one channel with
refcount 20 and that every session can read. Fails without the fix.
-
`ClientChannelManagerManagedTests.CreatorRetriesWhenLeaseReleasedDuringInitialOpenAsync`
(Core.Tests): holds the initial open with a mock transport, then
attaches and releases a second lease during it. It checks that the
creator gets a lease on a fresh entry. Fails without the fix with
"Channel is Closed."

Verification:
- **Local runs (net10.0, sequential):** Opc.Ua.Client.Tests 2778 passed;
Opc.Ua.Sessions.Tests 851 passed, 379 skipped; Opc.Ua.Core.Tests 6241
passed, 88 skipped.
- **net48:** Client.Tests and Core.Tests compile cleanly.
- **ServerLoadHarness repro:** `--client shared --sessions 20
--connect-parallel 4/20` now connects every run. The same build without
the fix still fails.

Note: when the creator's open is cut short this way, the `OpenChannel`
background failure is still logged at `fail` level even though the
creator now recovers.

## Related Issues

- None filed yet. Found while load-testing the shared-channel client
with the ServerLoadHarness.

## Checklist

- [x] I have signed the
[CLA](https://opcfoundation.org/license/cla/ContributorLicenseAgreementv1.0.pdf)
and read the
[CONTRIBUTING](https://github.com/OPCFoundation/UA-.NETStandard/blob/master/CONTRIBUTING.md)
doc.
- [x] I have added tests that prove my fix is effective or that my
feature works and increased code coverage.
- [x] I have added all necessary documentation.
- [x] I have verified that my changes do not introduce (new) build or
analyzer warnings.
- [ ] I ran **all** tests locally using the **UA.slnx** solution against
at least .net **framework** and .net **10**, and all passed.
- [ ] I fixed **all** failing and flaky tests in the CI pipelines and
**all** CodeQL warnings.
- [ ] I have addressed **all** PR feedback received.

🤖 Generated with [Claude Code](https://claude.com/claude-code)

---------

Co-authored-by: Claude Opus 5.5 <noreply@anthropic.com>
2026-10-12 07:04:41 +02:00