Signal-iOS/SignalServiceKit/Locking/TSMutex.swift
Adam Sharp f19110add6 Add TSMutex to replace UnfairLock
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.
2024-11-15 16:31:03 -05:00

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)
}
}