Renames UnfairLock to TSMutex and refreshes its interface to remove methods that are unused except in tests. TSMutex has a generic parameter State that supports non-sendable and non-copyable values. UnfairLock is a type alias of TSMutex<Void>. The withLock() function is also no longer inherited from NSLocking, instead being implemented to be similar to OSAllocatedUnfairLock's API. Finally, the class is audited for sendability and compiles without warnings under strict concurrency checking.
85 lines
2.7 KiB
Swift
85 lines
2.7 KiB
Swift
//
|
|
// Copyright 2024 Signal Messenger, LLC
|
|
// SPDX-License-Identifier: AGPL-3.0-only
|
|
//
|
|
|
|
internal import os.lock
|
|
|
|
@available(iOS, obsoleted: 16.0, message: "Use OSAllocatedUnfairLock instead.")
|
|
public typealias UnfairLock = TSMutex<Void>
|
|
|
|
/// A wrapper around os_unfair_lock to contain the memory management required to properly handle
|
|
/// allocating an instance of os_unfair_lock with a stable address in Swift.
|
|
///
|
|
/// > Important: os_unfair_lock is NOT reentrant.
|
|
///
|
|
/// > Warning: Errors with unfair lock are fatal and will terminate the process.
|
|
///
|
|
/// > Note: To be replaced with OSAllocatedUnfairLock once our underlying iOS version is ≥ 16.
|
|
@available(iOS, obsoleted: 16.0, message: "Use OSAllocatedUnfairLock instead.")
|
|
public final class TSMutex<State: ~Copyable>: Sendable {
|
|
@usableFromInline
|
|
nonisolated(unsafe) let _lock: os_unfair_lock_t
|
|
|
|
@usableFromInline
|
|
nonisolated(unsafe) var _state: State
|
|
|
|
public init(initialState state: consuming sending State) {
|
|
_lock = .allocate(capacity: 1)
|
|
_lock.initialize(to: os_unfair_lock_s())
|
|
_state = state
|
|
}
|
|
|
|
deinit {
|
|
_lock.deinitialize(count: 1).deallocate()
|
|
}
|
|
|
|
@inlinable
|
|
public func withLock<T: ~Copyable, E: Error>(_ body: @Sendable (inout State) throws(E) -> sending T) throws(E) -> sending T {
|
|
os_unfair_lock_lock(_lock)
|
|
defer { os_unfair_lock_unlock(_lock) }
|
|
return try body(&_state)
|
|
}
|
|
}
|
|
|
|
extension TSMutex where State == Void {
|
|
public convenience init() {
|
|
self.init(initialState: ())
|
|
}
|
|
|
|
@inlinable
|
|
public func withLock<T: ~Copyable, E: Error>(_ body: @Sendable () throws(E) -> sending T) throws(E) -> sending T {
|
|
try withLock { (_) throws(E) in
|
|
try body()
|
|
}
|
|
}
|
|
|
|
/// Locks the lock. Blocks if the lock is held by another thread.
|
|
/// Forwards to os_unfair_lock_lock() defined in os/lock.h
|
|
@inlinable
|
|
public final func lock() {
|
|
os_unfair_lock_lock(_lock)
|
|
}
|
|
|
|
/// Unlocks the lock. Fatal error if the lock is owned by another thread.
|
|
/// Forwards to os_unfair_lock_unlock() defined in os/lock.h
|
|
@inlinable
|
|
public final func unlock() {
|
|
os_unfair_lock_unlock(_lock)
|
|
}
|
|
|
|
/// Fatal assert that the lock is owned by the current thread.
|
|
/// Forwards to os_unfair_lock_assert_owner defined in os/lock.h
|
|
@inlinable
|
|
public final func assertOwner() {
|
|
os_unfair_lock_assert_owner(_lock)
|
|
}
|
|
|
|
/// Fatal assert that the lock is not owned by the current thread.
|
|
/// Forwards to os_unfair_lock_assert_not_owner defined in os/lock.h
|
|
@inlinable
|
|
public final func assertNotOwner() {
|
|
os_unfair_lock_assert_not_owner(_lock)
|
|
}
|
|
}
|