Make lazily-initialized list properties on generated POCOs thread-safe#69
Open
ewoutkramer wants to merge 1 commit into
Open
Make lazily-initialized list properties on generated POCOs thread-safe#69ewoutkramer wants to merge 1 commit into
ewoutkramer wants to merge 1 commit into
Conversation
Generated list-typed element properties used a plain `_field ??= []` pattern, which races on first access: two threads calling the getter concurrently while the backing field is still null can each allocate their own list, and only one ends up published to the field while the other silently gets discarded. Switching to LazyInitializer.EnsureInitialized publishes a single winning instance to every caller via an atomic CompareExchange, at effectively the same cost on the already-initialized hot path (a Volatile.Read instead of a plain field read). Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Summary
Generated list-typed element properties (e.g.
Patient.Identifier,DomainResource.Extension) use the pattern:This is not safe under concurrent access: if two threads call the getter while the backing field is still
null, both can allocate their ownList<>, and only one of those instances ends up published to the field — the other is silently discarded, along with anything already added to it by its caller. This affects every list-typed property across every generated FHIR release (found while analyzing thread-safety offirely-net-sdkPOCOs).This PR switches the getter to
LazyInitializer.EnsureInitialized(ref _field, () => []), which:Interlocked.CompareExchangeon the cold path), instead of letting losing callers walk away with their own orphaned list.Volatile.Readvs. a plain field read, essentially free on x86/x64.This is the same idiom already used elsewhere in the base model for the
Overflowdictionary and the annotations backing field (Hl7.Fhir.Base/Model/Base.cs), just not yet applied to the codegen template for per-element list properties.Scalar (non-list) properties are unaffected — they're plain field reads with no laziness, so no race exists there.
Verification
dotnet buildonMicrosoft.Health.Fhir.CodeGensucceeds.firely-net-sdksources: the only changes are the addedusing System.Threading;and the getter line for every list property (e.g.Patient.Identifier,Patient.Name,Patient.ContactComponent.Relationship, etc.) — see below for a representative excerpt.Note:
dotnet test'sTestFirelyHashes*golden-hash tests already fail on a clean checkout ofmainin this environment (file count mismatch, e.g. 159 vs. 163 forCSharpFirely2-R4-Satellite.json), independent of this change — that looks like local FHIR package cache drift rather than something this PR introduces, but flagging it since it means the golden hashes for this generator target aren't currently verifiable end-to-end here.Follow-up (not in this PR)
The regenerated
Model/Generated/*.csfiles infirely-net-sdkstill need to be regenerated and committed there to pick this up — that's a separate PR againstFirelyTeam/firely-net-sdk.🤖 Generated with Claude Code