Please let me know if TSAN is off topic, apologies.
Is TSAN (thread sanitiser on Xcode) the right way to detect data races?
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) ?
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()
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.
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()
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.