MobiusCore/Source/ConcurrentAccessDetector.swift (45 lines of code) (raw):

// Copyright Spotify AB. // SPDX-License-Identifier: Apache-2.0 import Foundation /// Utility to catch invalid concurrent access to non-thread-safe code. /// /// Like a mutex, `ConcurrentAccessDetector` guards a critical region. However, instead of blocking if two threads /// attempt to enter the critical region at once, it crashes in debug builds. In release builds, it has no effect (and /// also no overhead, as long as it’s only used within one module). /// /// Copies of a `ConcurrentAccessDetector` share the same underlying mutex, and hence their critical region is the union /// of the critical regions of all copies. struct ConcurrentAccessDetector { #if DEBUG // This is hidden in an inner final class because we want it to be an empty struct in non-debug builds private final class State { let lock = NSRecursiveLock() var lastSeenLocation: Location = Location(file: "", line: 0, queue: "") } private struct Location: CustomStringConvertible { var file: StaticString var line: UInt var queue: String var description: String { let shortFile = String(describing: file).split(separator: "/").last ?? "<unknown>" return "\(shortFile): \(line) on “\(queue)”" } } private let state = State() func `guard`<T>(file: StaticString = #file, line: UInt = #line, _ block: () throws -> T) rethrows -> T { let location = Location(file: file, line: line, queue: currentQueueLabel()) guard state.lock.try() else { preconditionFailure( """ Unpermitted concurrent access. Currently held by \(state.lastSeenLocation) Conflicting access by \(location) """, file: file, line: line ) } defer { state.lock.unlock() } state.lastSeenLocation = location return try block() } private func currentQueueLabel() -> String { let name = __dispatch_queue_get_label(nil) return String(cString: name, encoding: .utf8) ?? "" } #else // This currently needs to be explicitly annotated to be optimized out @inline(__always) func `guard`<T>(_ block: () throws -> T) rethrows -> T { return try block() } #endif }