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
Atomic
property 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 += 1
is 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
Atomic
property 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:
QueueTimer
suspend
andresume
functions first read thestate
, then write it if needed, then suspend the timer. This could result in thesuspend
function being called multiple times, same for theresume
. I think the best solution would be to protectstate
andtimer
using the same queue.Analytics.swift:62
we checkisActiveWriteKey
then in line 67 we add the active key. This is also a data race, that could result in the active key added toactiveWriteKeys
multiple times. MaybeactiveWriteKeys
should be aSet
to avoid duplicates?ConnectionMonitor
, theconnectionStatus
is updated every five minutes. Even if the usage ofAtomic
is flawed, I left it as is because of the large amount of time between calls.In general, I suggest to get rid of the
@Atomic
wrapper altogether, and just useDispatchQueue
s 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
😄