Synchronize ConditionState branch collection access#3895
Conversation
|
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## master378 #3895 +/- ##
=============================================
+ Coverage 60.12% 60.14% +0.01%
=============================================
Files 378 378
Lines 79061 79063 +2
Branches 13837 13838 +1
=============================================
+ Hits 47539 47552 +13
+ Misses 27099 27091 -8
+ Partials 4423 4420 -3
🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Pull request overview
This PR addresses a concurrency defect in ConditionState where the branch dictionary could be mutated while being enumerated from other threads, leading to crashes. It adds synchronization around branch access and updates branch enumeration to use a stable snapshot, plus a stress test to validate the fix.
Changes:
- Added a per-
ConditionStatelock to guard all reads/writes of the branch dictionary. - Changed
GetBranches()to return a snapshot copy to make enumeration safe under concurrent mutation. - Added a multithreaded stress test that concurrently creates/clears branches while enumerating/counting.
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| Stack/Opc.Ua.Core/Stack/State/ConditionState.cs | Synchronizes branch dictionary access and returns snapshot copies for safe enumeration. |
| Tests/Opc.Ua.Core.Tests/Stack/State/ConditionStateTests.cs | Adds a concurrency stress test for branch create/clear vs enumerate/count. |
Co-authored-by: Copilot Autofix powered by AI <[email protected]>
|
@copilot address PR feedback and ensure CI run is green |
|
@marcschier @copilot First (multiple times therefore it must be caught somewhere probably and is handled): The keep-alive read validates the returned value as Int32 (Opc.Ua.Client.Session::OnSendKeepAliveAsync(line 3091) and casts it to int (line 3109), Second: Is Flaky due to the posibility of NoteStateCollection:SaveAsNodeSet2() for the LastModified DateTime value to differ in precision on the two calls of the ExportNodesToNodeSet2 at two moments in time. |
|
@copilot, second test failure, test was fixed in master, port it here, investigate the first test issue, goal is ci run gree
|
…og noise - SessionTests.cs: fix keep-alive mock returning UInt32 (0u); changed to Int32 (0) so ValidateDataValue(typeof(int)) no longer logs BadTypeMismatch errors. - NodeStateCollection.SaveAsNodeSet2: add overload with DateTime? lastModified parameter so callers can pin the timestamp for deterministic output. - CoreClientUtils.ExportNodesToNodeSet2: add optional DateTime? lastModified parameter, passed through to SaveAsNodeSet2. - NodeSetExportTest.ExportNodesToNodeSet2_DefaultOptionsAsync: capture a single DateTime.UtcNow and pass it to both Default and Complete exports so the LastModified field is identical in both files, eliminating the flaky byte-count difference. Also switch to Assert.That/Is.LessThanOrEqualTo per project style.
Both issues addressed in the latest commit: First (keep-alive Second ( |
…5.378.152 (#21) This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [OPCFoundation.NetStandard.Opc.Ua](https://github.com/OPCFoundation/UA-.NETStandard) | `1.5.378.145` → `1.5.378.152` |  |  | --- ### Release Notes <details> <summary>OPCFoundation/UA-.NETStandard (OPCFoundation.NetStandard.Opc.Ua)</summary> ### [`v1.5.378.152`](https://github.com/OPCFoundation/UA-.NETStandard/releases/tag/1.5.378.152): OPC UA 1.05 Maintenance Update [Compare Source](OPCFoundation/UA-.NETStandard@1.5.378.145...1.5.378.152) Maintenance Release for fixing bugs found on the main378 development branch. #### Released packages [OPCFoundation.NetStandard.Opc.Ua](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.Core](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Core/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.Security.Certificates](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Security.Certificates/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.Configuration](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Configuration/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.Server](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Server/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.Client](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.Bindings.Https](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Bindings.Https/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.PubSub](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.PubSub/1.5.378.152) #### What's Changed - Fix memory leak: dispose linked CancellationTokenSource in ChannelAsyncOperation.EndAsync by [@​romanett](https://github.com/romanett) with [@​Copilot](https://github.com/Copilot) in [#​3812](OPCFoundation/UA-.NETStandard#3812) - Fix Race condition in OnPublishComplete by [@​markuswallen](https://github.com/markuswallen) in [#​3837](OPCFoundation/UA-.NETStandard#3837) - \[master378] Use request.SessionId instead of request.Session.Id in the ReportAuditCancelEvent call from CancelRequests. by [@​mrsuciu](https://github.com/mrsuciu) in [#​3853](OPCFoundation/UA-.NETStandard#3853) - Prune extra decoded children that should stay on the type definition only. by [@​mrsuciu](https://github.com/mrsuciu) in [#​3843](OPCFoundation/UA-.NETStandard#3843) - \[Server] Fix NRE in ActivateSession if Session times out before activation completes by [@​romanett](https://github.com/romanett) in [#​3844](OPCFoundation/UA-.NETStandard#3844) - \[Server] Fix [#​3896](OPCFoundation/UA-.NETStandard#3896): send full certificate chain when CA is in the issuer store (1.5.x) by [@​marcschier](https://github.com/marcschier) in [#​3898](OPCFoundation/UA-.NETStandard#3898) - Synchronize ConditionState branch collection access by [@​marcschier](https://github.com/marcschier) with [@​Copilot](https://github.com/Copilot) in [#​3895](OPCFoundation/UA-.NETStandard#3895) - Prepare 1.5.378 maintenence release by [@​mrsuciu](https://github.com/mrsuciu) in [#​3893](OPCFoundation/UA-.NETStandard#3893) **Full Changelog**: <OPCFoundation/UA-.NETStandard@1.5.378.145...1.5.378.152> </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM (`* 0-3 * * *`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDIuMCIsInVwZGF0ZWRJblZlciI6IjQzLjI0Mi4wIiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119--> Reviewed-on: https://codeberg.org/umati/connect/pulls/21
…r to 1.5.378.152 (#22) This PR contains the following updates: | Package | Change | [Age](https://docs.renovatebot.com/merge-confidence/) | [Confidence](https://docs.renovatebot.com/merge-confidence/) | |---|---|---|---| | [OPCFoundation.NetStandard.Opc.Ua.Server](https://github.com/OPCFoundation/UA-.NETStandard) | `1.5.378.145` → `1.5.378.152` |  |  | --- ### Release Notes <details> <summary>OPCFoundation/UA-.NETStandard (OPCFoundation.NetStandard.Opc.Ua.Server)</summary> ### [`v1.5.378.152`](https://github.com/OPCFoundation/UA-.NETStandard/releases/tag/1.5.378.152): OPC UA 1.05 Maintenance Update [Compare Source](OPCFoundation/UA-.NETStandard@1.5.378.145...1.5.378.152) Maintenance Release for fixing bugs found on the main378 development branch. #### Released packages [OPCFoundation.NetStandard.Opc.Ua](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.Core](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Core/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.Security.Certificates](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Security.Certificates/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.Configuration](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Configuration/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.Server](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Server/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.Client](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Client.ComplexTypes/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.Bindings.Https](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.Bindings.Https/1.5.378.152) [OPCFoundation.NetStandard.Opc.Ua.PubSub](https://www.nuget.org/packages/OPCFoundation.NetStandard.Opc.Ua.PubSub/1.5.378.152) #### What's Changed - Fix memory leak: dispose linked CancellationTokenSource in ChannelAsyncOperation.EndAsync by [@​romanett](https://github.com/romanett) with [@​Copilot](https://github.com/Copilot) in [#​3812](OPCFoundation/UA-.NETStandard#3812) - Fix Race condition in OnPublishComplete by [@​markuswallen](https://github.com/markuswallen) in [#​3837](OPCFoundation/UA-.NETStandard#3837) - \[master378] Use request.SessionId instead of request.Session.Id in the ReportAuditCancelEvent call from CancelRequests. by [@​mrsuciu](https://github.com/mrsuciu) in [#​3853](OPCFoundation/UA-.NETStandard#3853) - Prune extra decoded children that should stay on the type definition only. by [@​mrsuciu](https://github.com/mrsuciu) in [#​3843](OPCFoundation/UA-.NETStandard#3843) - \[Server] Fix NRE in ActivateSession if Session times out before activation completes by [@​romanett](https://github.com/romanett) in [#​3844](OPCFoundation/UA-.NETStandard#3844) - \[Server] Fix [#​3896](OPCFoundation/UA-.NETStandard#3896): send full certificate chain when CA is in the issuer store (1.5.x) by [@​marcschier](https://github.com/marcschier) in [#​3898](OPCFoundation/UA-.NETStandard#3898) - Synchronize ConditionState branch collection access by [@​marcschier](https://github.com/marcschier) with [@​Copilot](https://github.com/Copilot) in [#​3895](OPCFoundation/UA-.NETStandard#3895) - Prepare 1.5.378 maintenence release by [@​mrsuciu](https://github.com/mrsuciu) in [#​3893](OPCFoundation/UA-.NETStandard#3893) **Full Changelog**: <OPCFoundation/UA-.NETStandard@1.5.378.145...1.5.378.152> </details> --- ### Configuration 📅 **Schedule**: (UTC) - Branch creation - Between 12:00 AM and 03:59 AM (`* 0-3 * * *`) - Automerge - At any time (no schedule defined) 🚦 **Automerge**: Disabled by config. Please merge this manually once you are satisfied. ♻ **Rebasing**: Whenever PR becomes conflicted, or you tick the rebase/retry checkbox. 🔕 **Ignore**: Close this PR and you won't be reminded about this update again. --- - [ ] <!-- rebase-check -->If you want to rebase/retry this PR, check this box --- This PR has been generated by [Mend Renovate](https://github.com/renovatebot/renovate). <!--renovate-debug:eyJjcmVhdGVkSW5WZXIiOiI0My4yNDIuMCIsInVwZGF0ZWRJblZlciI6IjQzLjI0OS41IiwidGFyZ2V0QnJhbmNoIjoibWFpbiIsImxhYmVscyI6W119--> Reviewed-on: https://codeberg.org/umati/connect/pulls/22
Description
ConditionState.m_branches(a plainDictionary) was read and written from multiple threads without synchronization. A state change on a background thread could mutate the dictionary while readers (GetRetainState/UpdateRetainState,ConditionRefresh,GetBranch) enumerated it, yielding torn entries and native access violations (observed asIsBranchon a nullthis). This ports the master378 fix tomaster, where the type lives atStack/Opc.Ua.Core.Types/State/ConditionState.csand usesByteString/nullable refs.m_branchesLockguarding every read/write ofm_branches(CreateBranch,GetBranch,ReplaceBranchEvent,RemoveBranchEvent,ClearBranches,GetBranchCount).GetBranches()now returns a copy taken under the lock, so internal readers andAlarmHolder.GetBranchesForConditionRefreshiterate a stable collection while other threads mutate the live one. Public signature is unchanged.ConcurrentBranchAccessIsThreadSafe, stressing concurrent create/clear writers against enumerate/count readers.lockis used rather thanSemaphoreSlimbecause all affected methods are synchronous. EachConditionStateholds its own lock, so the recursiveUpdateRetainStatetraversal across branches neither contends nor deadlocks.ConcurrentDictionarywas rejected: it would change the public return type and still leave compound read-modify-write sequences (e.g.ReplaceBranchEvent) unprotected.Related Issues
Checklist
Put an
xin the boxes that apply. You can complete these step by step after opening the PR.