Skip to content

Commit 39e2c37

Browse files
committed
Adopt Mutex.
This PR adopts `Mutex` on all platforms except Darwin (where we still need to back-deploy further than `Mutex` is available.) Resolves #538.
1 parent c996b0a commit 39e2c37

File tree

5 files changed

+92
-225
lines changed

5 files changed

+92
-225
lines changed

Sources/Testing/CMakeLists.txt

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -89,7 +89,6 @@ add_library(Testing
8989
Support/Graph.swift
9090
Support/JSON.swift
9191
Support/Locked.swift
92-
Support/Locked+Platform.swift
9392
Support/VersionNumber.swift
9493
Support/Versions.swift
9594
Discovery+Macro.swift

Sources/Testing/ExitTests/WaitFor.swift

Lines changed: 39 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -80,7 +80,42 @@ func wait(for pid: consuming pid_t) async throws -> ExitStatus {
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 = LockedWith<pthread_mutex_t, [pid_t: CheckedContinuation<ExitStatus, any Error>]>()
83+
private nonisolated(unsafe) let _childProcessContinuations = {
84+
let result = ManagedBuffer<[pid_t: CheckedContinuation<ExitStatus, any Error>], pthread_mutex_t>.create(
85+
minimumCapacity: 1,
86+
makingHeaderWith: { _ in [:] }
87+
)
88+
89+
result.withUnsafeMutablePointers { sectionBounds, lock in
90+
_ = pthread_mutex_init(lock, nil)
91+
}
92+
93+
return result
94+
}()
95+
96+
/// Access the value in `_childProcessContinuations` while guarded by its lock.
97+
///
98+
/// - Parameters:
99+
/// - body: A closure to invoke while the lock is held.
100+
///
101+
/// - Returns: Whatever is returned by `body`.
102+
///
103+
/// - Throws: Whatever is thrown by `body`.
104+
private func _withLockedChildProcessContinuations<R>(
105+
_ body: (
106+
_ childProcessContinuations: inout [pid_t: CheckedContinuation<ExitStatus, any Error>],
107+
_ lock: UnsafeMutablePointer<pthread_mutex_t>
108+
) throws -> R
109+
) rethrows -> R {
110+
try _childProcessContinuations.withUnsafeMutablePointers { childProcessContinuations, lock in
111+
_ = pthread_mutex_lock(lock)
112+
defer {
113+
_ = pthread_mutex_unlock(lock)
114+
}
115+
116+
return try body(&childProcessContinuations.pointee, lock)
117+
}
118+
}
84119

85120
/// A condition variable used to suspend the waiter thread created by
86121
/// `_createWaitThread()` when there are no child processes to await.
@@ -116,7 +151,7 @@ private let _createWaitThread: Void = {
116151
var siginfo = siginfo_t()
117152
if 0 == waitid(P_ALL, 0, &siginfo, WEXITED | WNOWAIT) {
118153
if case let pid = siginfo.si_pid, pid != 0 {
119-
let continuation = _childProcessContinuations.withLock { childProcessContinuations in
154+
let continuation = _withLockedChildProcessContinuations { childProcessContinuations, _ in
120155
childProcessContinuations.removeValue(forKey: pid)
121156
}
122157

@@ -137,7 +172,7 @@ private let _createWaitThread: Void = {
137172
// newly-scheduled waiter process. (If this condition is spuriously
138173
// woken, we'll just loop again, which is fine.) Note that we read errno
139174
// outside the lock in case acquiring the lock perturbs it.
140-
_childProcessContinuations.withUnsafeUnderlyingLock { lock, childProcessContinuations in
175+
_withLockedChildProcessContinuations { childProcessContinuations, lock in
141176
if childProcessContinuations.isEmpty {
142177
_ = pthread_cond_wait(_waitThreadNoChildrenCondition, lock)
143178
}
@@ -209,7 +244,7 @@ func wait(for pid: consuming pid_t) async throws -> ExitStatus {
209244
_createWaitThread
210245

211246
return try await withCheckedThrowingContinuation { continuation in
212-
_childProcessContinuations.withLock { childProcessContinuations in
247+
_withLockedChildProcessContinuations { childProcessContinuations, _ in
213248
// We don't need to worry about a race condition here because waitid()
214249
// does not clear the wait/zombie state of the child process. If it sees
215250
// the child process has terminated and manages to acquire the lock before

Sources/Testing/Support/Locked+Platform.swift

Lines changed: 0 additions & 96 deletions
This file was deleted.

Sources/Testing/Support/Locked.swift

Lines changed: 50 additions & 84 deletions
Original file line numberDiff line numberDiff line change
@@ -8,38 +8,8 @@
88
// See https://swift.org/CONTRIBUTORS.txt for Swift project authors
99
//
1010

11-
internal import _TestingInternals
12-
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: -
11+
private import _TestingInternals
12+
private import Synchronization
4313

4414
/// A type that wraps a value requiring access from a synchronous caller during
4515
/// concurrent execution.
@@ -52,30 +22,48 @@ protocol Lockable {
5222
/// concurrency tools.
5323
///
5424
/// This type is not part of the public interface of the testing library.
55-
struct LockedWith<L, T>: RawRepresentable where L: Lockable {
56-
/// A type providing heap-allocated storage for an instance of ``Locked``.
57-
private final class _Storage: ManagedBuffer<T, L> {
58-
deinit {
59-
withUnsafeMutablePointerToElements { lock in
60-
L.deinitializeLock(at: lock)
61-
}
25+
struct Locked<T> {
26+
/// A type providing storage for the underlying lock and wrapped value.
27+
#if SWT_TARGET_OS_APPLE && canImport(os)
28+
private typealias _Storage = ManagedBuffer<T, os_unfair_lock_s>
29+
#else
30+
private final class _Storage {
31+
let mutex: Mutex<T>
32+
33+
init(_ rawValue: consuming sending T) {
34+
mutex = Mutex(rawValue)
6235
}
6336
}
37+
#endif
6438

6539
/// Storage for the underlying lock and wrapped value.
66-
private nonisolated(unsafe) var _storage: ManagedBuffer<T, L>
40+
private nonisolated(unsafe) var _storage: _Storage
41+
}
42+
43+
extension Locked: Sendable where T: Sendable {}
6744

45+
extension Locked: RawRepresentable {
6846
init(rawValue: T) {
69-
_storage = _Storage.create(minimumCapacity: 1, makingHeaderWith: { _ in rawValue })
47+
#if SWT_TARGET_OS_APPLE && canImport(os)
48+
_storage = .create(minimumCapacity: 1, makingHeaderWith: { _ in rawValue })
7049
_storage.withUnsafeMutablePointerToElements { lock in
71-
L.initializeLock(at: lock)
50+
lock.initialize(to: .init())
7251
}
52+
#else
53+
nonisolated(unsafe) let rawValue = rawValue
54+
_storage = _Storage(rawValue)
55+
#endif
7356
}
7457

7558
var rawValue: T {
76-
withLock { $0 }
59+
withLock { rawValue in
60+
nonisolated(unsafe) let rawValue = rawValue
61+
return rawValue
62+
}
7763
}
64+
}
7865

66+
extension Locked {
7967
/// Acquire the lock and invoke a function while it is held.
8068
///
8169
/// - Parameters:
@@ -88,55 +76,27 @@ struct LockedWith<L, T>: RawRepresentable where L: Lockable {
8876
/// This function can be used to synchronize access to shared data from a
8977
/// synchronous caller. Wherever possible, use actor isolation or other Swift
9078
/// concurrency tools.
91-
nonmutating func withLock<R>(_ body: (inout T) throws -> R) rethrows -> R where R: ~Copyable {
92-
try _storage.withUnsafeMutablePointers { rawValue, lock in
93-
L.unsafelyAcquireLock(at: lock)
79+
func withLock<R>(_ body: (inout T) throws -> sending R) rethrows -> sending R where R: ~Copyable {
80+
#if SWT_TARGET_OS_APPLE && canImport(os)
81+
nonisolated(unsafe) let result = try _storage.withUnsafeMutablePointers { rawValue, lock in
82+
os_unfair_lock_lock(lock)
9483
defer {
95-
L.unsafelyRelinquishLock(at: lock)
84+
os_unfair_lock_unlock(lock)
9685
}
9786
return try body(&rawValue.pointee)
9887
}
99-
}
100-
101-
/// Acquire the lock and invoke a function while it is held, yielding both the
102-
/// protected value and a reference to the underlying lock guarding it.
103-
///
104-
/// - Parameters:
105-
/// - body: A closure to invoke while the lock is held.
106-
///
107-
/// - Returns: Whatever is returned by `body`.
108-
///
109-
/// - Throws: Whatever is thrown by `body`.
110-
///
111-
/// This function is equivalent to ``withLock(_:)`` except that the closure
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.
117-
///
118-
/// - Warning: Callers that unlock the lock _must_ lock it again before the
119-
/// closure returns. If the lock is not acquired when `body` returns, the
120-
/// effect is undefined.
121-
nonmutating func withUnsafeUnderlyingLock<R>(_ body: (UnsafeMutablePointer<L>, T) throws -> R) rethrows -> R where R: ~Copyable {
122-
try withLock { value in
123-
try _storage.withUnsafeMutablePointerToElements { lock in
124-
try body(lock, value)
125-
}
88+
return result
89+
#else
90+
try _storage.mutex.withLock { rawValue in
91+
try body(&rawValue)
12692
}
93+
#endif
12794
}
12895
}
12996

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-
13797
// MARK: - Additions
13898

139-
extension LockedWith where T: AdditiveArithmetic {
99+
extension Locked where T: AdditiveArithmetic & Sendable {
140100
/// Add something to the current wrapped value of this instance.
141101
///
142102
/// - Parameters:
@@ -152,7 +112,7 @@ extension LockedWith where T: AdditiveArithmetic {
152112
}
153113
}
154114

155-
extension LockedWith where T: Numeric {
115+
extension Locked where T: Numeric & Sendable {
156116
/// Increment the current wrapped value of this instance.
157117
///
158118
/// - Returns: The sum of ``rawValue`` and `1`.
@@ -172,7 +132,7 @@ extension LockedWith where T: Numeric {
172132
}
173133
}
174134

175-
extension LockedWith {
135+
extension Locked {
176136
/// Initialize an instance of this type with a raw value of `nil`.
177137
init<V>() where T == V? {
178138
self.init(rawValue: nil)
@@ -188,3 +148,9 @@ extension LockedWith {
188148
self.init(rawValue: [])
189149
}
190150
}
151+
152+
// MARK: - POSIX conveniences
153+
154+
#if os(FreeBSD) || os(OpenBSD)
155+
typealias pthread_mutex_t = _TestingInternals.pthread_mutex_t?
156+
#endif

0 commit comments

Comments
 (0)