Fix: concurrency issue #143
Open
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.
In the old SDK version, the
getFeatureValuemethod was not protected from concurrent access:No thread synchronization: The
getFeatureValuemethod directly accessed the sharedevalContextwithout any synchronization.Mutation of shared state: The
getEvalContext()method mutated the existingevalContext.stackContextdirectly:Race condition on
evaluatedFeatures: WhengetFeatureValuewas called concurrently from different threads, multiple threads simultaneously tried to modifyStackContext.evaluatedFeatures(which is aSet<String>). This led to:SetinvariantsEXC_BAD_ACCESS)No protection during features update: The
featuresFetchedSuccessfullymethod updatedevalContextwithout synchronization, which could happen simultaneously with reads ingetFeatureValue.Solution
In the new version, the following changes were made:
Added serial queue for synchronization:
All operations with shared state wrapped in syncQueue:
getFeatureValueusessyncQueue.syncfor synchronous accessfeaturesFetchedSuccessfullyusessyncQueue.asyncwith[weak self]for asynchronous updatesevalContextandgbContext.featuresare protectedCreating new context instead of mutation:
Instead of mutating the existing
evalContext, thegetEvalContext()method now creates a newEvalContextwith a newStackContext():This ensures that each thread works with its own isolated context, avoiding race conditions.
Atomic reading of shared state:
All reads of
evalContextandgbContext.featuresoccur atomically withinsyncQueue.sync, preventing situations where one thread reads data while another modifies it.