Skip to content

Commit 63eb1d9

Browse files
authored
Use os_unfair_lock on Darwin when available. (#942)
This PR refactors the `Locked` type to be generic over the type of lock it uses, allowing us to adopt `os_unfair_lock` in most places, but also allowing us to use other lock types as needed (i.e. `pthread_mutex_t` in WaitFor.swift if libdispatch is unavailable.) Under `-O`, locking and unlocking is fully inlined, while initialization (which is relatively rare) is partially, but not completely, inlined. ### Checklist: - [x] Code and documentation should follow the style of the [Style Guide](https://github.com/apple/swift-testing/blob/main/Documentation/StyleGuide.md). - [x] If public symbols are renamed or modified, DocC references should be updated.
1 parent 57335d7 commit 63eb1d9

File tree

7 files changed

+210
-83
lines changed

7 files changed

+210
-83
lines changed

Sources/Testing/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -79,6 +79,7 @@ add_library(Testing
7979
Support/Graph.swift
8080
Support/JSON.swift
8181
Support/Locked.swift
82+
Support/Locked+Platform.swift
8283
Support/SystemError.swift
8384
Support/Versions.swift
8485
Discovery.swift

Sources/Testing/ExitTests/WaitFor.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,7 @@ func wait(for pid: consuming pid_t) async throws -> ExitCondition {
8080
}
8181
#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD)
8282
/// A mapping of awaited child PIDs to their corresponding Swift continuations.
83-
private let _childProcessContinuations = Locked<[pid_t: CheckedContinuation<ExitCondition, any Error>]>()
83+
private let _childProcessContinuations = LockedWith<pthread_mutex_t, [pid_t: CheckedContinuation<ExitCondition, any Error>]>()
8484

8585
/// A condition variable used to suspend the waiter thread created by
8686
/// `_createWaitThread()` when there are no child processes to await.
@@ -137,7 +137,7 @@ private let _createWaitThread: Void = {
137137
// newly-scheduled waiter process. (If this condition is spuriously
138138
// woken, we'll just loop again, which is fine.) Note that we read errno
139139
// outside the lock in case acquiring the lock perturbs it.
140-
_childProcessContinuations.withUnsafePlatformLock { lock, childProcessContinuations in
140+
_childProcessContinuations.withUnsafeUnderlyingLock { lock, childProcessContinuations in
141141
if childProcessContinuations.isEmpty {
142142
_ = pthread_cond_wait(_waitThreadNoChildrenCondition, lock)
143143
}
Lines changed: 96 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,96 @@
1+
//
2+
// This source file is part of the Swift.org open source project
3+
//
4+
// Copyright (c) 2023–2025 Apple Inc. and the Swift project authors
5+
// Licensed under Apache License v2.0 with Runtime Library Exception
6+
//
7+
// See https://swift.org/LICENSE.txt for license information
8+
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
9+
//
10+
11+
internal import _TestingInternals
12+
13+
extension Never: Lockable {
14+
static func initializeLock(at lock: UnsafeMutablePointer<Self>) {}
15+
static func deinitializeLock(at lock: UnsafeMutablePointer<Self>) {}
16+
static func unsafelyAcquireLock(at lock: UnsafeMutablePointer<Self>) {}
17+
static func unsafelyRelinquishLock(at lock: UnsafeMutablePointer<Self>) {}
18+
}
19+
20+
#if SWT_TARGET_OS_APPLE && !SWT_NO_OS_UNFAIR_LOCK
21+
extension os_unfair_lock_s: Lockable {
22+
static func initializeLock(at lock: UnsafeMutablePointer<Self>) {
23+
lock.initialize(to: .init())
24+
}
25+
26+
static func deinitializeLock(at lock: UnsafeMutablePointer<Self>) {
27+
// No deinitialization needed.
28+
}
29+
30+
static func unsafelyAcquireLock(at lock: UnsafeMutablePointer<Self>) {
31+
os_unfair_lock_lock(lock)
32+
}
33+
34+
static func unsafelyRelinquishLock(at lock: UnsafeMutablePointer<Self>) {
35+
os_unfair_lock_unlock(lock)
36+
}
37+
}
38+
#endif
39+
40+
#if os(FreeBSD) || os(OpenBSD)
41+
typealias pthread_mutex_t = _TestingInternals.pthread_mutex_t?
42+
#endif
43+
44+
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) || os(FreeBSD) || os(OpenBSD)
45+
extension pthread_mutex_t: Lockable {
46+
static func initializeLock(at lock: UnsafeMutablePointer<Self>) {
47+
_ = pthread_mutex_init(lock, nil)
48+
}
49+
50+
static func deinitializeLock(at lock: UnsafeMutablePointer<Self>) {
51+
_ = pthread_mutex_destroy(lock)
52+
}
53+
54+
static func unsafelyAcquireLock(at lock: UnsafeMutablePointer<Self>) {
55+
_ = pthread_mutex_lock(lock)
56+
}
57+
58+
static func unsafelyRelinquishLock(at lock: UnsafeMutablePointer<Self>) {
59+
_ = pthread_mutex_unlock(lock)
60+
}
61+
}
62+
#endif
63+
64+
#if os(Windows)
65+
extension SRWLOCK: Lockable {
66+
static func initializeLock(at lock: UnsafeMutablePointer<Self>) {
67+
InitializeSRWLock(lock)
68+
}
69+
70+
static func deinitializeLock(at lock: UnsafeMutablePointer<Self>) {
71+
// No deinitialization needed.
72+
}
73+
74+
static func unsafelyAcquireLock(at lock: UnsafeMutablePointer<Self>) {
75+
AcquireSRWLockExclusive(lock)
76+
}
77+
78+
static func unsafelyRelinquishLock(at lock: UnsafeMutablePointer<Self>) {
79+
ReleaseSRWLockExclusive(lock)
80+
}
81+
}
82+
#endif
83+
84+
#if SWT_TARGET_OS_APPLE && !SWT_NO_OS_UNFAIR_LOCK
85+
typealias DefaultLock = os_unfair_lock
86+
#elseif SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded)) || os(FreeBSD) || os(OpenBSD)
87+
typealias DefaultLock = pthread_mutex_t
88+
#elseif os(Windows)
89+
typealias DefaultLock = SRWLOCK
90+
#elseif os(WASI)
91+
// No locks on WASI without multithreaded runtime.
92+
typealias DefaultLock = Never
93+
#else
94+
#warning("Platform-specific implementation missing: locking unavailable")
95+
typealias DefaultLock = Never
96+
#endif

Sources/Testing/Support/Locked.swift

Lines changed: 63 additions & 74 deletions
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,7 @@
11
//
22
// This source file is part of the Swift.org open source project
33
//
4-
// Copyright (c) 2023 Apple Inc. and the Swift project authors
4+
// Copyright (c) 2023–2025 Apple Inc. and the Swift project authors
55
// Licensed under Apache License v2.0 with Runtime Library Exception
66
//
77
// See https://swift.org/LICENSE.txt for license information
@@ -10,6 +10,37 @@
1010

1111
internal import _TestingInternals
1212

13+
/// A protocol defining a type, generally platform-specific, that satisfies the
14+
/// requirements of a lock or mutex.
15+
protocol Lockable {
16+
/// Initialize the lock at the given address.
17+
///
18+
/// - Parameters:
19+
/// - lock: A pointer to uninitialized memory that should be initialized as
20+
/// an instance of this type.
21+
static func initializeLock(at lock: UnsafeMutablePointer<Self>)
22+
23+
/// Deinitialize the lock at the given address.
24+
///
25+
/// - Parameters:
26+
/// - lock: A pointer to initialized memory that should be deinitialized.
27+
static func deinitializeLock(at lock: UnsafeMutablePointer<Self>)
28+
29+
/// Acquire the lock at the given address.
30+
///
31+
/// - Parameters:
32+
/// - lock: The address of the lock to acquire.
33+
static func unsafelyAcquireLock(at lock: UnsafeMutablePointer<Self>)
34+
35+
/// Relinquish the lock at the given address.
36+
///
37+
/// - Parameters:
38+
/// - lock: The address of the lock to relinquish.
39+
static func unsafelyRelinquishLock(at lock: UnsafeMutablePointer<Self>)
40+
}
41+
42+
// MARK: -
43+
1344
/// A type that wraps a value requiring access from a synchronous caller during
1445
/// concurrent execution.
1546
///
@@ -21,67 +52,23 @@ internal import _TestingInternals
2152
/// concurrency tools.
2253
///
2354
/// This type is not part of the public interface of the testing library.
24-
///
25-
/// - Bug: The state protected by this type should instead be protected using
26-
/// actor isolation, but actor-isolated functions cannot be called from
27-
/// synchronous functions. ([83888717](rdar://83888717))
28-
struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
29-
/// The platform-specific type to use for locking.
30-
///
31-
/// It would be preferable to implement this lock in Swift, however there is
32-
/// no standard lock or mutex type available across all platforms that is
33-
/// visible in Swift. C11 has a standard `mtx_t` type, but it is not widely
34-
/// supported and so cannot be relied upon.
35-
///
36-
/// To keep the implementation of this type as simple as possible,
37-
/// `pthread_mutex_t` is used on Apple platforms instead of `os_unfair_lock`
38-
/// or `OSAllocatedUnfairLock`.
39-
#if SWT_TARGET_OS_APPLE || os(Linux) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
40-
typealias PlatformLock = pthread_mutex_t
41-
#elseif os(FreeBSD) || os(OpenBSD)
42-
typealias PlatformLock = pthread_mutex_t?
43-
#elseif os(Windows)
44-
typealias PlatformLock = SRWLOCK
45-
#elseif os(WASI)
46-
// No locks on WASI without multithreaded runtime.
47-
typealias PlatformLock = Never
48-
#else
49-
#warning("Platform-specific implementation missing: locking unavailable")
50-
typealias PlatformLock = Never
51-
#endif
52-
55+
struct LockedWith<L, T>: RawRepresentable where L: Lockable {
5356
/// A type providing heap-allocated storage for an instance of ``Locked``.
54-
private final class _Storage: ManagedBuffer<T, PlatformLock> {
57+
private final class _Storage: ManagedBuffer<T, L> {
5558
deinit {
5659
withUnsafeMutablePointerToElements { lock in
57-
#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
58-
_ = pthread_mutex_destroy(lock)
59-
#elseif os(Windows)
60-
// No deinitialization needed.
61-
#elseif os(WASI)
62-
// No locks on WASI without multithreaded runtime.
63-
#else
64-
#warning("Platform-specific implementation missing: locking unavailable")
65-
#endif
60+
L.deinitializeLock(at: lock)
6661
}
6762
}
6863
}
6964

7065
/// Storage for the underlying lock and wrapped value.
71-
private nonisolated(unsafe) var _storage: ManagedBuffer<T, PlatformLock>
66+
private nonisolated(unsafe) var _storage: ManagedBuffer<T, L>
7267

7368
init(rawValue: T) {
7469
_storage = _Storage.create(minimumCapacity: 1, makingHeaderWith: { _ in rawValue })
7570
_storage.withUnsafeMutablePointerToElements { lock in
76-
#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
77-
_ = pthread_mutex_init(lock, nil)
78-
#elseif os(Windows)
79-
InitializeSRWLock(lock)
80-
#elseif os(WASI)
81-
// No locks on WASI without multithreaded runtime.
82-
#else
83-
#warning("Platform-specific implementation missing: locking unavailable")
84-
#endif
71+
L.initializeLock(at: lock)
8572
}
8673
}
8774

@@ -103,28 +90,16 @@ struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
10390
/// concurrency tools.
10491
nonmutating func withLock<R>(_ body: (inout T) throws -> R) rethrows -> R {
10592
try _storage.withUnsafeMutablePointers { rawValue, lock in
106-
#if SWT_TARGET_OS_APPLE || os(Linux) || os(FreeBSD) || os(OpenBSD) || os(Android) || (os(WASI) && compiler(>=6.1) && _runtime(_multithreaded))
107-
_ = pthread_mutex_lock(lock)
93+
L.unsafelyAcquireLock(at: lock)
10894
defer {
109-
_ = pthread_mutex_unlock(lock)
95+
L.unsafelyRelinquishLock(at: lock)
11096
}
111-
#elseif os(Windows)
112-
AcquireSRWLockExclusive(lock)
113-
defer {
114-
ReleaseSRWLockExclusive(lock)
115-
}
116-
#elseif os(WASI)
117-
// No locks on WASI without multithreaded runtime.
118-
#else
119-
#warning("Platform-specific implementation missing: locking unavailable")
120-
#endif
121-
12297
return try body(&rawValue.pointee)
12398
}
12499
}
125100

126101
/// Acquire the lock and invoke a function while it is held, yielding both the
127-
/// protected value and a reference to the lock itself.
102+
/// protected value and a reference to the underlying lock guarding it.
128103
///
129104
/// - Parameters:
130105
/// - body: A closure to invoke while the lock is held.
@@ -134,16 +109,16 @@ struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
134109
/// - Throws: Whatever is thrown by `body`.
135110
///
136111
/// This function is equivalent to ``withLock(_:)`` except that the closure
137-
/// passed to it also takes a reference to the underlying platform lock. This
138-
/// function can be used when platform-specific functionality such as a
139-
/// `pthread_cond_t` is needed. Because the caller has direct access to the
140-
/// lock and is able to unlock and re-lock it, it is unsafe to modify the
141-
/// protected value.
112+
/// passed to it also takes a reference to the underlying lock guarding this
113+
/// instance's wrapped value. This function can be used when platform-specific
114+
/// functionality such as a `pthread_cond_t` is needed. Because the caller has
115+
/// direct access to the lock and is able to unlock and re-lock it, it is
116+
/// unsafe to modify the protected value.
142117
///
143118
/// - Warning: Callers that unlock the lock _must_ lock it again before the
144119
/// closure returns. If the lock is not acquired when `body` returns, the
145120
/// effect is undefined.
146-
nonmutating func withUnsafePlatformLock<R>(_ body: (UnsafeMutablePointer<PlatformLock>, T) throws -> R) rethrows -> R {
121+
nonmutating func withUnsafeUnderlyingLock<R>(_ body: (UnsafeMutablePointer<L>, T) throws -> R) rethrows -> R {
147122
try withLock { value in
148123
try _storage.withUnsafeMutablePointerToElements { lock in
149124
try body(lock, value)
@@ -152,7 +127,16 @@ struct Locked<T>: RawRepresentable, Sendable where T: Sendable {
152127
}
153128
}
154129

155-
extension Locked where T: AdditiveArithmetic {
130+
extension LockedWith: Sendable where T: Sendable {}
131+
132+
/// A type that wraps a value requiring access from a synchronous caller during
133+
/// concurrent execution and which uses the default platform-specific lock type
134+
/// for the current platform.
135+
typealias Locked<T> = LockedWith<DefaultLock, T>
136+
137+
// MARK: - Additions
138+
139+
extension LockedWith where T: AdditiveArithmetic {
156140
/// Add something to the current wrapped value of this instance.
157141
///
158142
/// - Parameters:
@@ -168,7 +152,7 @@ extension Locked where T: AdditiveArithmetic {
168152
}
169153
}
170154

171-
extension Locked where T: Numeric {
155+
extension LockedWith where T: Numeric {
172156
/// Increment the current wrapped value of this instance.
173157
///
174158
/// - Returns: The sum of ``rawValue`` and `1`.
@@ -188,7 +172,7 @@ extension Locked where T: Numeric {
188172
}
189173
}
190174

191-
extension Locked {
175+
extension LockedWith {
192176
/// Initialize an instance of this type with a raw value of `nil`.
193177
init<V>() where T == V? {
194178
self.init(rawValue: nil)
@@ -198,4 +182,9 @@ extension Locked {
198182
init<K, V>() where T == Dictionary<K, V> {
199183
self.init(rawValue: [:])
200184
}
185+
186+
/// Initialize an instance of this type with a raw value of `[]`.
187+
init<V>() where T == [V] {
188+
self.init(rawValue: [])
189+
}
201190
}

Sources/_TestingInternals/include/Includes.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -131,6 +131,10 @@
131131
#if !SWT_NO_DYNAMIC_LINKING
132132
#include <mach-o/dyld.h>
133133
#endif
134+
135+
#if !SWT_NO_OS_UNFAIR_LOCK
136+
#include <os/lock.h>
137+
#endif
134138
#endif
135139

136140
#if defined(__FreeBSD__)

0 commit comments

Comments
 (0)