Search code examples
swiftlockingdata-racethread-sanitizer

Swift access race with os_unfair_lock_lock


I made a custom property wrapper which provides a method to access data in a mutually exclusive context using an os_unfair_lock. After testing my wrapper with TSAN enabled, an access race error was reported at the point of lock acquisition using os_unfair_lock_lock (shown in the image below)

Access race image

Somehow a locking structure which is supposedly thread-safe is reported by TSAN as not being so. What is going on here?


Solution

  • According to the WWDC 2016 talk "Concurrent Programming with GCD in Swift 3" at around 18:07 the speaker states that

    Traditionally you would use a lock. And in Swift, since you have the entire Darwin module at your disposition, you will actually see the struct based traditional C locks. However [emphasis added], Swift assumes that anything that's a struct can be moved, and that doesn't work with a mutex or with a lock.

    The solution is to bridge to Objective-C and create a class which wraps the os_unfair_lock as an ivar:

    And if you want something that's smaller and that looks like the locks that you have in C, then you have to call into Objective-C and introduce a base class in Objective-C that has your lock as an ivar

    In this case, something like

    UnfairLock.h

    #ifndef UnfairLock_h
    #define UnfairLock_h
    
    @import Foundation;
    @import os;
    
    @interface UnfairLock : NSObject
    
    -(void)unfairlyAcquire;
    -(void)unlock;
    
    @end
    
    
    #endif /* UnfairLock_h */
    

    UnfairLock.m

    #import <Foundation/Foundation.h>
    #import "UnfairLock.h"
    
    @implementation UnfairLock {
        os_unfair_lock _lock;
    }
    
    -(instancetype)init {
        self = [super init];
        
        if (self) {
            _lock = OS_UNFAIR_LOCK_INIT;
        }
        
        return self;
    }
    
    
    -(void)unfairlyAcquire {
        os_unfair_lock_lock(&_lock);
    }
    
    -(void)unlock {
        os_unfair_lock_unlock(&_lock);
    }
    
    @end