Skip to content

Commit 894c3b6

Browse files
committed
backporting Mutex
1 parent 6bce19f commit 894c3b6

File tree

2 files changed

+179
-0
lines changed

2 files changed

+179
-0
lines changed

Sources/Mutex.swift

+108
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,108 @@
1+
//
2+
// Mutex.swift
3+
// AllocatedLock
4+
//
5+
// Created by Simon Whitty on 07/09/2024.
6+
// Copyright 2024 Simon Whitty
7+
//
8+
// Distributed under the permissive MIT license
9+
// Get the latest version from here:
10+
//
11+
// https://github.com/swhitty/AllocatedLock
12+
//
13+
// Permission is hereby granted, free of charge, to any person obtaining a copy
14+
// of this software and associated documentation files (the "Software"), to deal
15+
// in the Software without restriction, including without limitation the rights
16+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17+
// copies of the Software, and to permit persons to whom the Software is
18+
// furnished to do so, subject to the following conditions:
19+
//
20+
// The above copyright notice and this permission notice shall be included in all
21+
// copies or substantial portions of the Software.
22+
//
23+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29+
// SOFTWARE.
30+
//
31+
32+
// Backports the Swift 6 type Mutex<Value> to all Darwin platforms
33+
public struct Mutex<Value>: @unchecked Sendable {
34+
let lock: AllocatedLock<Value> // Compatible with OSAllocatedUnfairLock iOS 16+
35+
}
36+
37+
#if compiler(>=6)
38+
public extension Mutex {
39+
init(_ initialValue: consuming sending Value) {
40+
self.lock = AllocatedLock(uncheckedState: initialValue)
41+
}
42+
43+
borrowing func withLock<Result, E: Error>(
44+
_ body: (inout sending Value) throws(E) -> sending Result
45+
) throws(E) -> sending Result {
46+
do {
47+
return try lock.withLockUnchecked { value in
48+
nonisolated(unsafe) var copy = value
49+
defer { value = copy }
50+
return try Transferring(body(&copy))
51+
}.value
52+
} catch let error as E {
53+
throw error
54+
} catch {
55+
preconditionFailure("cannot occur")
56+
}
57+
}
58+
59+
borrowing func withLockIfAvailable<Result, E>(
60+
_ body: (inout sending Value) throws(E) -> sending Result
61+
) throws(E) -> sending Result? where E: Error {
62+
do {
63+
return try lock.withLockIfAvailableUnchecked { value in
64+
nonisolated(unsafe) var copy = value
65+
defer { value = copy }
66+
return try Transferring(body(&copy))
67+
}?.value
68+
} catch let error as E {
69+
throw error
70+
} catch {
71+
preconditionFailure("cannot occur")
72+
}
73+
}
74+
}
75+
#else
76+
public extension Mutex {
77+
init(_ initialValue: consuming Value) {
78+
self.lock = AllocatedLock(uncheckedState: initialValue)
79+
}
80+
81+
borrowing func withLock<Result>(
82+
_ body: (inout Value) throws -> Result
83+
) rethrows -> Result {
84+
try lock.withLockUnchecked {
85+
return try body(&$0)
86+
}
87+
}
88+
89+
borrowing func withLockIfAvailable<Result>(
90+
_ body: (inout Value) throws -> Result
91+
) rethrows -> Result? {
92+
try lock.withLockIfAvailableUnchecked {
93+
return try body(&$0)
94+
}
95+
}
96+
}
97+
#endif
98+
99+
100+
#if compiler(>=6)
101+
struct Transferring<T> {
102+
nonisolated(unsafe) var value: T
103+
104+
init(_ value: T) {
105+
self.value = value
106+
}
107+
}
108+
#endif

Tests/MutexTests.swift

+71
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,71 @@
1+
//
2+
// MutexTests.swift
3+
// AllocatedLock
4+
//
5+
// Created by Simon Whitty on 07/09/2024.
6+
// Copyright 2024 Simon Whitty
7+
//
8+
// Distributed under the permissive MIT license
9+
// Get the latest version from here:
10+
//
11+
// https://github.com/swhitty/AllocatedLock
12+
//
13+
// Permission is hereby granted, free of charge, to any person obtaining a copy
14+
// of this software and associated documentation files (the "Software"), to deal
15+
// in the Software without restriction, including without limitation the rights
16+
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
17+
// copies of the Software, and to permit persons to whom the Software is
18+
// furnished to do so, subject to the following conditions:
19+
//
20+
// The above copyright notice and this permission notice shall be included in all
21+
// copies or substantial portions of the Software.
22+
//
23+
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
24+
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
25+
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
26+
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
27+
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
28+
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
29+
// SOFTWARE.
30+
//
31+
32+
@testable import AllocatedLock
33+
import XCTest
34+
35+
final class MutexTests: XCTestCase {
36+
37+
func testWithLock_ReturnsValue() {
38+
let mutex = Mutex("fish")
39+
let val = mutex.withLock {
40+
$0 + " & chips"
41+
}
42+
XCTAssertEqual(val, "fish & chips")
43+
}
44+
45+
func testWithLock_ThrowsError() {
46+
let mutex = Mutex("fish")
47+
XCTAssertThrowsError(try mutex.withLock { _ -> Void in throw CancellationError() }) {
48+
_ = $0 is CancellationError
49+
}
50+
}
51+
52+
func testLockIfAvailable_ReturnsValue() {
53+
let mutex = Mutex("fish")
54+
mutex.lock.unsafeLock()
55+
XCTAssertNil(
56+
mutex.withLockIfAvailable { _ in "chips" }
57+
)
58+
mutex.lock.unsafeUnlock()
59+
XCTAssertEqual(
60+
mutex.withLockIfAvailable { _ in "chips" },
61+
"chips"
62+
)
63+
}
64+
65+
func testWithLockIfAvailable_ThrowsError() {
66+
let mutex = Mutex("fish")
67+
XCTAssertThrowsError(try mutex.withLockIfAvailable { _ -> Void in throw CancellationError() }) {
68+
_ = $0 is CancellationError
69+
}
70+
}
71+
}

0 commit comments

Comments
 (0)