-
Notifications
You must be signed in to change notification settings - Fork 2
Cache Concurrency Guard #117
Copy link
Copy link
Labels
Area: RepositoriesRelates to the `ITopicRepository` interface or one of its implementations.Relates to the `ITopicRepository` interface or one of its implementations.Priority: 1Severity 2: MajorStatus 5: CompleteTask is considered complete, and ready for deployment.Task is considered complete, and ready for deployment.Type: BugBehavior that is inconsistent with documented or expected behavior.Behavior that is inconsistent with documented or expected behavior.Type: ImprovementImproves the functionality or interface of an existing feature.Improves the functionality or interface of an existing feature.
Milestone
Description
Activity
Metadata
Metadata
Assignees
Labels
Area: RepositoriesRelates to the `ITopicRepository` interface or one of its implementations.Relates to the `ITopicRepository` interface or one of its implementations.Priority: 1Severity 2: MajorStatus 5: CompleteTask is considered complete, and ready for deployment.Task is considered complete, and ready for deployment.Type: BugBehavior that is inconsistent with documented or expected behavior.Behavior that is inconsistent with documented or expected behavior.Type: ImprovementImproves the functionality or interface of an existing feature.Improves the functionality or interface of an existing feature.
Background
Lazy loading introduces a concurrency bug into the shared
CachedTopicRepositorysingleton where reading a topic's children (or other properties) used to be a safe read, but now triggers an in-place merge with no synchronization, so two concurrent requests hitting the same unloaded topic can throw aInvalidOperationExceptionor corrupt the state.Solution
The fix is a per-topic async gate (not a global lock) that serializes loads of the same topic while leaving unrelated topics fully concurrent, with the gate safely reclaimed once a topic is fully loaded (relying on the fact these boundaries only ever progress from
LoadState.NotLoadedtoLoadState.Loaded).SemaphoreSlimgate with double-checked-locking pattern inEnsureLoaded()Note: This explicitly excludes associations (i.e., relationships and references), since their
LoadStateis dynamic; those will be handled separately (#128).