* Fix publish/dispose race that hangs Client tests
Subscription.Dispose pre-disposed m_publishTimer and m_messageWorkerCts
before calling ResetPublishTimerAndWorkerState, causing Cancel() to throw
ObjectDisposedException and leaving the publish worker stuck. Removed the
duplicate disposal so the reset method is the single owner of those resources,
and guarded Cancel/Dispose with try/catch ObjectDisposedException for defense
in depth.
Session.OnPublishComplete could run after Session.Dispose disposed
m_reconnectLock, throwing ObjectDisposedException from SemaphoreSlim.Wait()
and re-arming via the throttle path indefinitely. Added an early Disposed
guard, wrapped Wait/Release in try/catch ObjectDisposedException, and added
a Disposed guard in QueueBeginPublish so the throttle cannot reschedule
publishes after disposal.
Fixes the macOS net10.0 hang seen in Azure pipeline build 13257
(TransferSubscriptionAsync, ReconnectWithSavedSessionSecretsAsync).
* fix lockout test and use UserName token for SuccessfulAuthenticationClearsFailedAttempts test
After c1e11b1ab made anonymous activations no-op for clearing the lockout
counter, the test must use a real (non-anonymous) credential to demonstrate
that successful authentication resets failed attempts. Use user1/password
with PolicyId=1 (UserName_1 in the reference server config).
* Fix DirectoryCertificateStore cache staleness across instances
When two DirectoryCertificateStore instances point at the same directory (e.g. a CertificateValidator's internal store and a separate store opened by a caller), the directory LastWriteTimeUtc seen by one DirectoryInfo handle does not reliably reflect modifications made through another handle on Windows, even after Refresh(). The mtime-based cache check therefore returned stale entries indefinitely, causing VerifyRejectedCertsDoNotOverflowStoreAsync to flake when the test fixture's RejectedStore failed to observe deletes performed by the validator's internal store.
Add a cheap on-disk file count cross-check in the cache-hit branch: if the count of cert files on disk does not match the cached entry count, force a reload. This avoids re-parsing every certificate when nothing has changed but reliably detects external modifications.
* test: fix keep-alive mock to return Int32 ServerState
The mock for the Server_ServerStatus_State read returned new Variant(0u)
(UInt32). The keep-alive worker expects Int32 (ServerState enum is
wire-encoded as Int32), so ClientBase.ValidateDataValue logged a
BadTypeMismatch warning on every keep-alive throughout the test run.
Real servers always emit Int32 for enum variables (BinaryEncoder
downgrades the enum encoding byte to Int32), so the warning was purely a
test-mock artifact. Switch the mock to new Variant((int)ServerState.Running)
to eliminate the spurious warnings.
* Source generator: emit and consume ModelDependencyAttribute. Emit one [assembly: ModelDependencyAttribute(...)] per model the
generated assembly emits and per upstream model it transitively
consumes (new ModelDependencyGenerator in
Tools/Opc.Ua.SourceGeneration.Core).
* Replace the unused PEReader-based ReferenceMetadataScanner with a
Roslyn IAssemblySymbol.GetAttributes() scan
(ReferencedModelDependencyScanner in
Tools/Opc.Ua.SourceGeneration). Hooks the per-symbol incremental
cache, works for both PortableExecutableReference and
CompilationReference, AOT-safe.
* Wire the scanner result through ModelSourceGenerator into
ModelCompilation. The reduced URI-keyed map is passed to the public
Generators.GenerateCode extension methods.
* NodesetFileCollection.GetDesignFileListForModel now consults the
referenced-model map, so transitive nodeset dependencies that are
satisfied by a referenced assembly no longer raise the
missing-dependency error.
* Override resolution: when a local AdditionalFile and a referenced
assembly both declare the same model URI under the same C# prefix
the local generation is silently skipped (avoids CS0101); different
prefixes still generate so downstream consumers can intentionally
override types under a different namespace.
* New diagnostics MODELGEN010 (override skipped) and MODELGEN012
(multiple referenced assemblies provide same model URI -> tie-break
on highest version+publication date).
* Bump asserted GeneratedSources counts in ModelGeneratorTests for the
new *.ModelDependencies.g.cs files (one per generated model).
* Fix unrelated CS1574 broken cref in NodeId.TryParseLongForm xmldoc
that was blocking the Opc.Ua.Types build.
* Document the new flow in Docs/ModelDependencies.md.
* tests for ModelDependencyAttribute flow
* Refactor recently added "parsing" constructors to ParseLongForm static method. Method delegates to Parse(IServiceMessageContext)
NodeId, ExpandedNodeId and QualifiedName ParseLongForm overloads are now thin wrappers around Parse(context, options) using NodeIdParsingOptions with RequireResolvedUris=true and updateTables=false. Adds the FallbackToStringIdentifier opt-in to preserve the legacy lenient quirk for callers that need it (off by default for strict parity).
Tests for the refactored long-form parsers were added to the existing per-type test fixtures.
* Update nuget dependencies to latest
3.1 KiB
Cross-Assembly Model Dependencies
The OPC UA source generator emits [assembly: Opc.Ua.ModelDependencyAttribute(...)]
metadata on every assembly that has nodesets or design files in <AdditionalFiles>.
The attribute records, for each model the assembly emits or transitively consumes,
the model URI, the C# namespace prefix the generator used, and (when known) the
version and publication date.
Downstream consumers that reference such an assembly no longer need to re-add
those upstream nodesets to their own <AdditionalFiles>. The generator scans the
attributes on referenced assemblies and uses them to:
- Suppress the missing-dependency error in nodeset dependency collection when the missing namespace is known to be supplied by a referenced assembly.
- Apply override resolution when a local
<AdditionalFiles>entry refers to a model URI that is also in a referenced assembly:- Same C# prefix → local generation is silently skipped (the referenced
assembly already supplies the types; duplicate emission would cause
CS0101). - Different C# prefix → local generation proceeds; types live in a different C# namespace so no symbol conflict occurs.
- Same C# prefix → local generation is silently skipped (the referenced
assembly already supplies the types; duplicate emission would cause
- Tie-break when more than one referenced assembly provides the same model
URI by selecting the entry with the highest
(Version, PublicationDate)lexicographic tuple. The losing entries are reported asMODELGEN012info diagnostics.
Diagnostics
| ID | Severity | Meaning |
|---|---|---|
MODELGEN010 |
Info | Local model skipped because a referenced assembly provides it. |
MODELGEN012 |
Info | Multiple referenced assemblies provide the same model URI. |
Implementation
The Roslyn-side scan lives in
Tools/Opc.Ua.SourceGeneration/ReferencedModelDependencyScanner.cs and uses
IAssemblySymbol.GetAttributes() so it works for both PortableExecutableReference
and CompilationReference, hooks Roslyn's per-symbol incremental cache, avoids
file IO, and is AOT-safe inside the generator.
The emitter lives in
Tools/Opc.Ua.SourceGeneration.Core/Generators/ModelDependencyGenerator.cs
and produces one {prefix}.ModelDependencies.g.cs per generated model
containing assembly-attribute lines for the model itself and every model it
consumes (including the closure recovered from referenced assemblies).
Limitations (Phase 1)
This first cut keeps cross-assembly type resolution out of scope. When a
nodeset's transitive namespace is satisfied only by a referenced assembly the
generator skips the missing-dependency error but does not wire that
namespace's type definitions into the validator. Downstream users that need to
emit references to types from those upstream models (e.g. subtypes, complex
type fields, method input/output arguments) must still add the upstream design
or nodeset files to <AdditionalFiles> for now. A follow-up will register the
referenced model prefixes with ModelDesignValidator so that no AdditionalFile
duplication is required.