fix: Atomic increment is now performed within one enqueued operation #330
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.
The current implementation of the
Atomicproperty wrapper is sometimes used in a way that results in race conditions.When used like this:
It doesn't result in correct atomic operations. This is also applicable to operations that read the Atomic value, followed by a write based on the read value.
This is because the line
counter += 1is expanded as:And there is nothing preventing a thread to suspend in the middle of these two operations, so that multiple calls to a function containing an increment could be linearized as:
This PR fixes a couple of such cases, by adding a new method to the
Atomicproperty wrapper that allows for more complex operations. Using the new method instead of directly performing the increment fixes the issue:This PR also adds a unit test just for the Atomic wrapper that demonstrates the issue.
There are another couple cases that I left as they are since I'm not so sure how to fix them:
QueueTimersuspendandresumefunctions first read thestate, then write it if needed, then suspend the timer. This could result in thesuspendfunction being called multiple times, same for theresume. I think the best solution would be to protectstateandtimerusing the same queue.Analytics.swift:62we checkisActiveWriteKeythen in line 67 we add the active key. This is also a data race, that could result in the active key added toactiveWriteKeysmultiple times. MaybeactiveWriteKeysshould be aSetto avoid duplicates?ConnectionMonitor, theconnectionStatusis updated every five minutes. Even if the usage ofAtomicis flawed, I left it as is because of the large amount of time between calls.In general, I suggest to get rid of the
@Atomicwrapper altogether, and just useDispatchQueues directly. It will likely result in less queues being managed by the system, and less tricky mistakes like this one. Even better if you move toactor😄