Search code examples
iosswiftthread-safetygrand-central-dispatchthread-sanitizer

Thread Sanitizer in xcode giving wrong error


func doSomething() -> Int {
    var sum = 0
    let increaseWork = DispatchWorkItem {
        sum = sum + 100 //point 1
    }
    DispatchQueue.global().async(execute:increaseWork)
    increaseWork.wait()
    return sum //point 2
}

Thread Sanitizer is saying that there is race condition between point 1 and point 2. But I don't think there is any race condition as increaseWork.wait() is blocking call and it will not pass until the closure is executed.


Solution

  • There could be a race condition between those 2 points. Imagine what happens if you execute the doSomething() function on 2 separate threads like this:

    1. The first thread executes the increaseWork() closure and finishes it. It's at the line with wait right now
    2. The second thread starts executing and hits the async execute instruction
    3. The first thread hits the return instruction as the wait can continue
    4. At the same time, the closure from the second is scheduled and executed

    At this point, you can't tell for sure what is executed first: the sum = sum + 100 from the second thread or the return sum from the first one.

    The idea is that sum is a shared resource which is not synchronised, so, in theory, that could be a race condition. Even if you took care that such thing does not happen, the Thread Sanitizer detects a possible race condition as it does not know whether you start a single thread or you execute doSomething() function from 100 different threads at the same time.

    UPDATE:

    As I missed the fact that the sum variable is local, the above explanation does not answer the current question. The scenario described would never take place in the given piece of code.

    But, even though the sum is a local variable, due to the fact it is used and retained inside a closure, it will be allocated on heap, rather than on stack. That's because the Swift compiler cannot determine whether the closure will be finished its execution before the doSomething() function return or not.

    Why? Because the closure is passed to a constructor which behaves like an @escaping parameter. It implies that there is no guarantee when the closure will be executed, thus all the variables retained by the closure must be allocated on heap for safety. Not knowing when the closure will be executed, the Thread Sanitizer cannot determine that the return sum statement will be indeed executed after the closure finishes.

    So even though here we can be sure that no race condition can happen, the Thread Sanitizer raises an alarm, as it cannot determine it cannot happen.