Skip to content

Commit b55a65b

Browse files
authored
Merge pull request #5 from swhitty/non-copyable
Mutex should be ~Copyable
2 parents c7c4773 + da6e0f3 commit b55a65b

File tree

6 files changed

+342
-63
lines changed

6 files changed

+342
-63
lines changed

.github/workflows/build.yml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -71,7 +71,7 @@ jobs:
7171

7272
linux_swift_6_1:
7373
runs-on: ubuntu-latest
74-
container: swift:6.1
74+
container: swift:6.1.2
7575
steps:
7676
- name: Checkout
7777
uses: actions/checkout@v4
@@ -123,7 +123,7 @@ jobs:
123123

124124
linux_swift_6_1_musl:
125125
runs-on: ubuntu-latest
126-
container: swift:6.1
126+
container: swift:6.1.2
127127
steps:
128128
- name: Checkout
129129
uses: actions/checkout@v4
@@ -146,7 +146,7 @@ jobs:
146146
- name: Install Swift
147147
uses: SwiftyLab/setup-swift@latest
148148
with:
149-
swift-version: "6.1.0"
149+
swift-version: "6.1.2"
150150
- name: Version
151151
run: swift --version
152152
- name: Build

Sources/AllocatedLock.swift

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,7 @@
3030
//
3131

3232
// Backports the Swift interface around OSAllocatedUnfairLock available in recent Darwin platforms
33+
@available(*, deprecated, message: "Unused by Mutex and will be removed in future versions.")
3334
public struct AllocatedLock<State>: @unchecked Sendable {
3435

3536
@usableFromInline

Sources/Mutex.swift

Lines changed: 155 additions & 56 deletions
Original file line numberDiff line numberDiff line change
@@ -29,82 +29,181 @@
2929
// SOFTWARE.
3030
//
3131

32+
#if compiler(>=6)
33+
34+
#if !canImport(WinSDK)
35+
3236
// Backports the Swift 6 type Mutex<Value> to all Darwin platforms
3337

34-
// @available(macOS, deprecated: 15.0, message: "use Mutex from Synchronization module included with Swift 6")
35-
// @available(iOS, deprecated: 18.0, message: "use Mutex from Synchronization module included with Swift 6")
36-
// @available(tvOS, deprecated: 18.0, message: "use Mutex from Synchronization module included with Swift 6")
37-
// @available(watchOS, deprecated: 11.0, message: "use Mutex from Synchronization module included with Swift 6")
38-
// @available(visionOS, deprecated: 2.0, message: "use Mutex from Synchronization module included with Swift 6")
39-
public struct Mutex<Value>: Sendable {
40-
let lock: AllocatedLock<Value> // Compatible with OSAllocatedUnfairLock iOS 16+
38+
@available(macOS, introduced: 13.0, deprecated: 15.0, message: "use Mutex from Synchronization module")
39+
@available(iOS, introduced: 16.0, deprecated: 18.0, message: "use Mutex from Synchronization module")
40+
@available(tvOS, introduced: 18.0, deprecated: 15.0, message: "use Mutex from Synchronization module")
41+
@available(watchOS, introduced: 11.0, deprecated: 15.0, message: "use Mutex from Synchronization module")
42+
@available(visionOS, introduced: 2.0, deprecated: 15.0, message: "use Mutex from Synchronization module")
43+
public struct Mutex<Value: ~Copyable>: @unchecked Sendable, ~Copyable {
44+
let storage: Storage<Value>
45+
46+
public init(_ initialValue: consuming sending Value) {
47+
self.storage = Storage(initialValue)
48+
}
49+
50+
public borrowing func withLock<Result, E: Error>(
51+
_ body: (inout sending Value) throws(E) -> sending Result
52+
) throws(E) -> sending Result {
53+
storage.lock()
54+
defer { storage.unlock() }
55+
return try body(&storage.value)
56+
}
57+
58+
public borrowing func withLockIfAvailable<Result, E: Error>(
59+
_ body: (inout sending Value) throws(E) -> sending Result
60+
) throws(E) -> sending Result? {
61+
guard storage.tryLock() else { return nil }
62+
defer { storage.unlock() }
63+
return try body(&storage.value)
64+
}
4165
}
4266

43-
#if compiler(>=6)
44-
public extension Mutex {
45-
init(_ initialValue: consuming sending Value) {
46-
self.lock = AllocatedLock(uncheckedState: initialValue)
67+
#else
68+
69+
// Windows doesn't support ~Copyable yet
70+
71+
public struct Mutex<Value>: @unchecked Sendable {
72+
let storage: Storage<Value>
73+
74+
public init(_ initialValue: consuming sending Value) {
75+
self.storage = Storage(initialValue)
4776
}
4877

49-
borrowing func withLock<Result, E: Error>(
78+
public borrowing func withLock<Result, E: Error>(
5079
_ body: (inout sending Value) throws(E) -> sending Result
5180
) throws(E) -> sending Result {
52-
do {
53-
return try lock.withLockUnchecked { value in
54-
nonisolated(unsafe) var copy = value
55-
defer { value = copy }
56-
return try Transferring(body(&copy))
57-
}.value
58-
} catch let error as E {
59-
throw error
60-
} catch {
61-
preconditionFailure("cannot occur")
62-
}
63-
}
64-
65-
borrowing func withLockIfAvailable<Result, E>(
81+
storage.lock()
82+
defer { storage.unlock() }
83+
return try body(&storage.value)
84+
}
85+
86+
public borrowing func withLockIfAvailable<Result, E: Error>(
6687
_ body: (inout sending Value) throws(E) -> sending Result
67-
) throws(E) -> sending Result? where E: Error {
68-
do {
69-
return try lock.withLockIfAvailableUnchecked { value in
70-
nonisolated(unsafe) var copy = value
71-
defer { value = copy }
72-
return try Transferring(body(&copy))
73-
}?.value
74-
} catch let error as E {
75-
throw error
76-
} catch {
77-
preconditionFailure("cannot occur")
78-
}
88+
) throws(E) -> sending Result? {
89+
guard storage.tryLock() else { return nil }
90+
defer { storage.unlock() }
91+
return try body(&storage.value)
7992
}
8093
}
81-
private struct Transferring<T> {
82-
nonisolated(unsafe) var value: T
8394

84-
init(_ value: T) {
85-
self.value = value
95+
#endif
96+
97+
#if canImport(Darwin)
98+
99+
import struct os.os_unfair_lock_t
100+
import struct os.os_unfair_lock
101+
import func os.os_unfair_lock_lock
102+
import func os.os_unfair_lock_unlock
103+
import func os.os_unfair_lock_trylock
104+
105+
final class Storage<Value: ~Copyable> {
106+
private let _lock: os_unfair_lock_t
107+
var value: Value
108+
109+
init(_ initialValue: consuming Value) {
110+
self._lock = .allocate(capacity: 1)
111+
self._lock.initialize(to: os_unfair_lock())
112+
self.value = initialValue
113+
}
114+
115+
func lock() {
116+
os_unfair_lock_lock(_lock)
117+
}
118+
119+
func unlock() {
120+
os_unfair_lock_unlock(_lock)
121+
}
122+
123+
func tryLock() -> Bool {
124+
os_unfair_lock_trylock(_lock)
125+
}
126+
127+
deinit {
128+
self._lock.deinitialize(count: 1)
129+
self._lock.deallocate()
86130
}
87131
}
132+
133+
#elseif canImport(Glibc) || canImport(Musl) || canImport(Bionic)
134+
135+
#if canImport(Musl)
136+
import Musl
137+
#elseif canImport(Bionic)
138+
import Android
88139
#else
89-
public extension Mutex {
140+
import Glibc
141+
#endif
142+
143+
final class Storage<Value: ~Copyable> {
144+
private let _lock: UnsafeMutablePointer<pthread_mutex_t>
145+
146+
var value: Value
147+
90148
init(_ initialValue: consuming Value) {
91-
self.lock = AllocatedLock(uncheckedState: initialValue)
149+
var attr = pthread_mutexattr_t()
150+
pthread_mutexattr_init(&attr)
151+
self._lock = .allocate(capacity: 1)
152+
let err = pthread_mutex_init(self._lock, &attr)
153+
precondition(err == 0, "pthread_mutex_init error: \(err)")
154+
self.value = initialValue
155+
}
156+
157+
func lock() {
158+
let err = pthread_mutex_lock(_lock)
159+
precondition(err == 0, "pthread_mutex_lock error: \(err)")
92160
}
93161

94-
borrowing func withLock<Result>(
95-
_ body: (inout Value) throws -> Result
96-
) rethrows -> Result {
97-
try lock.withLockUnchecked {
98-
return try body(&$0)
99-
}
162+
func unlock() {
163+
let err = pthread_mutex_unlock(_lock)
164+
precondition(err == 0, "pthread_mutex_unlock error: \(err)")
100165
}
101166

102-
borrowing func withLockIfAvailable<Result>(
103-
_ body: (inout Value) throws -> Result
104-
) rethrows -> Result? {
105-
try lock.withLockIfAvailableUnchecked {
106-
return try body(&$0)
107-
}
167+
func tryLock() -> Bool {
168+
pthread_mutex_trylock(_lock) == 0
169+
}
170+
171+
deinit {
172+
let err = pthread_mutex_destroy(self._lock)
173+
precondition(err == 0, "pthread_mutex_destroy error: \(err)")
174+
self._lock.deallocate()
108175
}
109176
}
177+
178+
#elseif canImport(WinSDK)
179+
180+
import ucrt
181+
import WinSDK
182+
183+
final class Storage<Value> {
184+
private let _lock: UnsafeMutablePointer<SRWLOCK>
185+
186+
var value: Value
187+
188+
init(_ initialValue: Value) {
189+
self._lock = .allocate(capacity: 1)
190+
InitializeSRWLock(self._lock)
191+
self.value = initialValue
192+
}
193+
194+
func lock() {
195+
AcquireSRWLockExclusive(_lock)
196+
}
197+
198+
func unlock() {
199+
ReleaseSRWLockExclusive(_lock)
200+
}
201+
202+
func tryLock() -> Bool {
203+
TryAcquireSRWLockExclusive(_lock) != 0
204+
}
205+
}
206+
207+
#endif
208+
110209
#endif

0 commit comments

Comments
 (0)