I'm having a hard time relying on TSAN to detect problems due to its rightful insistence on reporting data-races (I know, stick with me). Picture the following implementation of a lazily-allocated property in an Obj-C class:
@interface MyClass {
id _myLazyValue; // starts as nil as all other Obj-C ivars
}
@end
@implementation MyClass
- (id)myLazyValue {
if (_myLazyValue == nil) {
@synchronized(self) {
if (_myLazyValue == nil) {
_myLazyValue = <expensive computation>
}
}
}
return _myLazyValue;
}
@end
The first line in the method is reading a pointer-sized chunk of memory outside of the protection provided by the @synchronized(...)
statement. That same value may be written by a different thread within the execution of the @synchronized
block. This is what TSAN complains about, but I need it not to.
The code above ensures the ivar is written by at most one thread. The read is unguarded, but it is impossible for any thread to read a non-nil value back that is invalid, uninitialized or unretained.
Why go through this trouble? Such a lazily-allocated property usually locks on @synchronized
once, until (at most) one thread does any work. Other threads may be temporarily waiting on the same lock but again only while the value is being initialized. The cost of allocation and initialization is guaranteed to be paid once: multiple threads cannot initialize the value multiple times (that’s the reason for the second _myLazyValue == nil
check within the scope of the @synchronized
block). Subsequent accesses of the initialized property skip locking altogether, which is exactly the performance we want from a lazily-allocated, immutable property that still guarantees thread-safe access.
Assuming there isn't a big embarrassing hole in my logic, is there a way to decorate specific portions of our sources (akin to #pragma statements that disable certain warnings) so that you can mark any read/write access to a specific value as "safe"? Is the most granular tool for this purpose the __attribute__((no_sanitize("thread")))
? Ideally one would want to ask TSAN to ignore only specific read/writes, rather than the entire body of a function.
Thank you!