ThreadSanitizer: data race

Hi everyone,

I don’t know if it’s the good place to ask for this, so if it’s not, please be kind enough to tell me where I should post this question.

I’m having a hard time figuring out why, since I activated ThreadSanitizer to my Xcode 9 scheme, I keep seeing race conditions when using OperationQueue and a custom BlockOperation subclass I made to deal with Asynchronous operations.

If I refer to the documentation on Operation, it says that isCancelled, isFinished, isExecuting properties must be thread safe since you never know from which thread they can be invoked. So I decided to go for a pthread_mutex_lock/unlock to protect the critical parts. For instance isFinished looks like this:

class AsyncBlockOperation : BlockOperation {

  enum State: String {
    case ready = "Ready",
          executing = "Executing",
          finished = "Finished"
    fileprivate var keyPath: String {
      return "is" + self.rawValue
    }
  }

  private var state: State = .ready
  {
    willSet {
      willChangeValue(forKey: state.keyPath)
      willChangeValue(forKey: newValue.keyPath)
    }
    didSet {
      didChangeValue(forKey: oldValue.keyPath)
      didChangeValue(forKey: state.keyPath)
    }
  }
  
  private var mutex = pthread_mutex_t()

  override var isFinished: Bool {
    pthread_mutex_lock(&mutex)
    defer { pthread_mutex_unlock(&mutex) }
    return state == .finished
  }

/* Same goes for isCancelled, and isExecuting… */
}

I thought that the problem of « thread-safe » would be solved with these lock/unlock mutex but when an other thread accesses one of the these properties I get a data race warning by the ThreadSanitizer.

What do I do wrong?
Is it possible that ThreadSanitizer reports a false positive? How should I debug this kind of issue?

Any help would be much appreciated. I can provide a Xcode test project with a running example of this issue.

Thanks in advance.

Best regards,
Thierry

Hi Thierry,

Are you initializing the mutex with pthread_mutex_init()? For example:

  pthread_mutex_init(&mutex, nil)

Just calling the default initializer for pthread_mutex_t is not sufficient — you won’t get any mutual exclusion. Thread Sanitizer should be complaining about this already (something like “Use of an uninitialized or destroyed mutex”).

If that doesn’t fix the problem, would you mind filing a bug at bugs.swift.org <Issues · apple/swift · GitHub; and attaching your test project? That will help us narrow it down and we can continue the investigation there.

Thanks,
Devin

···

On Jul 25, 2017, at 8:23 AM, Thierry Passeron via swift-users <swift-users@swift.org> wrote:

Hi everyone,

I don’t know if it’s the good place to ask for this, so if it’s not, please be kind enough to tell me where I should post this question.

I’m having a hard time figuring out why, since I activated ThreadSanitizer to my Xcode 9 scheme, I keep seeing race conditions when using OperationQueue and a custom BlockOperation subclass I made to deal with Asynchronous operations.

If I refer to the documentation on Operation, it says that isCancelled, isFinished, isExecuting properties must be thread safe since you never know from which thread they can be invoked. So I decided to go for a pthread_mutex_lock/unlock to protect the critical parts. For instance isFinished looks like this:

class AsyncBlockOperation : BlockOperation {

enum State: String {
   case ready = "Ready",
         executing = "Executing",
         finished = "Finished"
   fileprivate var keyPath: String {
     return "is" + self.rawValue
   }
}

private var state: State = .ready
{
   willSet {
     willChangeValue(forKey: state.keyPath)
     willChangeValue(forKey: newValue.keyPath)
   }
   didSet {
     didChangeValue(forKey: oldValue.keyPath)
     didChangeValue(forKey: state.keyPath)
   }
}

private var mutex = pthread_mutex_t()

override var isFinished: Bool {
   pthread_mutex_lock(&mutex)
   defer { pthread_mutex_unlock(&mutex) }
   return state == .finished
}

/* Same goes for isCancelled, and isExecuting… */
}

I thought that the problem of « thread-safe » would be solved with these lock/unlock mutex but when an other thread accesses one of the these properties I get a data race warning by the ThreadSanitizer.

What do I do wrong?
Is it possible that ThreadSanitizer reports a false positive? How should I debug this kind of issue?

Any help would be much appreciated. I can provide a Xcode test project with a running example of this issue.

Thanks in advance.

Best regards,
Thierry

_______________________________________________
swift-users mailing list
swift-users@swift.org
https://lists.swift.org/mailman/listinfo/swift-users

Thanks Devin, that was the problem!
After initialising with the recursive type attribute, it is now working.

This list is my saviour. Thank you guys! I hope I can make it up to you someday.

···

Le 25 juil. 2017 à 22:37, Devin Coughlin <dcoughlin@apple.com> a écrit :

pthread_mutex_init(&mutex, nil)

I'm running into a similar issue using SwiftWebSocket/WebSocket.swift at master · tidwall/SwiftWebSocket · GitHub

As far as I can tell the mutex is properly setup, but I still get a Swift access race when pthread_mutex_lock is called (11b5).

Ah, never mind: Fix non-thread-safe mutex access by IanHoar · Pull Request #141 · tidwall/SwiftWebSocket · GitHub