TSAN for detecting data race

Hi,

Please let me know if TSAN is off topic, apologies.

  1. Is TSAN (thread sanitiser on Xcode) the right way to detect data races?

  2. If I uncomment the print statements, the data races are detected. However If leave the print statements commented out, no data race is detected by TSAN, why is that?
    Am I missing something or is this a bug on Xcode 14.0 beta 2 (14A5229c) ?

  3. Should I be using some other tool / way to detect data races?

Code

class Counter {
    var value = 0
    
    func f1() {
        value = value + 1
    }
}

let counter = Counter()

Task.detached(priority: .background) {
//    print("thread = \(Thread.current)")
    counter.f1()
}

Task.detached(priority: .background) {
//    print("thread = \(Thread.current)")
    counter.f1()
}

RunLoop.main.run()

Environment

Xcode 14.0 beta 2 (14A5229c)
macOS: 13.0 Beta (22A5286j)

If the thread used to run the first Task exits before the thread for the second Task is spawned, from TSan's perspective there's no data race as it doesn't know that this is coincidental rather than the result of valid sequencing. Adding the print is probably just slowing things down enough so that both threads exist at the same time.

2 Likes

Interestingly TSAN can detect concurrent accesses even if they are far apart and obviously not happening at the same time (like in this example accesses to the shared state are split by some good 40 seconds):

class Counter {
    var value = 0
}

let counter = Counter()

Thread {
    // some processing that takes ~10 sec
    counter.value = 1
    // some processing that takes ~50 sec
}.start()

Thread {
    // some processing that takes ~50 sec
    counter.value = 2
    // some processing that takes ~10 sec
}.start()

RunLoop.main.run()

I wonder how it does this :thinking:

1 Like

Thanks @tgoyne and @tera

As @tera stated my understanding (based on WWDC videos) is that TSAN is capable of detecting data races that are hard to reproduce in real life.

It would be great if someone could test the same code on Xcode 13 on macOS 12 (I don't have that setup)

Just wondering if it is worth filing a feedback

Submitted feedback FB10553839

The amount of time that has passed is not directly relevant for TSan. Each time memory is read or written it records information like which thread it happened on, which locks are held, and which threads are alive, and compares that to the previously recorded information for the same memory address. In your case, both threads were alive when the racing accesses were made and there was no locks held for both, so it reports a race.

The overlapping lifetimes part is relevant for things like:

var value = 0
Thread {
    value += 1
}.start()

This is not a data race and TSan correctly does not report anything, but TSan can't distinguish this from the scenario in the OP.