Skip to content

Commit da42d84

Browse files
committed
Task+SleepIndefinitley
1 parent 243bbb8 commit da42d84

File tree

7 files changed

+366
-13
lines changed

7 files changed

+366
-13
lines changed

Package.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,7 +5,7 @@ import PackageDescription
55
let package = Package(
66
name: "Timeout",
77
platforms: [
8-
.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6)
8+
.macOS(.v10_15), .iOS(.v13), .tvOS(.v13), .watchOS(.v6), .visionOS(.v1)
99
],
1010
products: [
1111
.library(

Sources/Mutex.swift

Lines changed: 110 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,110 @@
1+
//
2+
// Mutex.swift
3+
// swift-mutex
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/swift-mutex
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+
#if compiler(>=6)
33+
34+
#if canImport(Darwin)
35+
// Backports the Synchronization.Mutex API for earlier Darwin platforms
36+
37+
@usableFromInline
38+
struct Mutex<Value>: @unchecked Sendable {
39+
let storage: Storage
40+
41+
@usableFromInline
42+
init(_ initialValue: consuming sending Value) {
43+
self.storage = Storage(initialValue)
44+
}
45+
46+
@usableFromInline
47+
borrowing func withLock<Result, E: Error>(
48+
_ body: (inout sending Value) throws(E) -> sending Result
49+
) throws(E) -> sending Result {
50+
storage.lock()
51+
defer { storage.unlock() }
52+
return try body(&storage.value)
53+
}
54+
55+
@usableFromInline
56+
borrowing func withLockIfAvailable<Result, E>(
57+
_ body: (inout sending Value) throws(E) -> sending Result
58+
) throws(E) -> sending Result? where E: Error {
59+
guard storage.tryLock() else { return nil }
60+
defer { storage.unlock() }
61+
return try body(&storage.value)
62+
}
63+
}
64+
65+
import struct os.os_unfair_lock_t
66+
import struct os.os_unfair_lock
67+
import func os.os_unfair_lock_lock
68+
import func os.os_unfair_lock_unlock
69+
import func os.os_unfair_lock_trylock
70+
71+
extension Mutex {
72+
73+
final class Storage {
74+
private let _lock: os_unfair_lock_t
75+
76+
var value: Value
77+
78+
init(_ initialValue: Value) {
79+
self._lock = .allocate(capacity: 1)
80+
self._lock.initialize(to: os_unfair_lock())
81+
self.value = initialValue
82+
}
83+
84+
func lock() {
85+
os_unfair_lock_lock(_lock)
86+
}
87+
88+
func unlock() {
89+
os_unfair_lock_unlock(_lock)
90+
}
91+
92+
func tryLock() -> Bool {
93+
os_unfair_lock_trylock(_lock)
94+
}
95+
96+
deinit {
97+
self._lock.deinitialize(count: 1)
98+
self._lock.deallocate()
99+
}
100+
}
101+
}
102+
103+
#elseif canImport(Synchronization)
104+
105+
import Synchronization
106+
107+
typealias Mutex = Synchronization.Mutex
108+
109+
#endif
110+
#endif
Lines changed: 64 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,64 @@
1+
//
2+
// Task+SleepIndefinitely.swift
3+
// swift-timeout
4+
//
5+
// Created by Simon Whitty on 02/06/2025.
6+
// Copyright 2025 Simon Whitty
7+
//
8+
// Distributed under the permissive MIT license
9+
// Get the latest version from here:
10+
//
11+
// https://github.com/swhitty/swift-timeout
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+
#if compiler(>=6)
33+
34+
extension Task<Never, Never> {
35+
36+
private typealias State = (isCancelled: Bool, continuation: CheckedContinuation<Never, any Error>?)
37+
38+
static func sleepIndefinitely() async throws -> Never {
39+
let state = Mutex<State>((isCancelled: false, continuation: nil))
40+
return try await withTaskCancellationHandler {
41+
try await withCheckedThrowingContinuation { continuation in
42+
let isCancelled = state.withLock {
43+
if $0.isCancelled {
44+
return true
45+
} else {
46+
$0.continuation = continuation
47+
return false
48+
}
49+
}
50+
if isCancelled {
51+
continuation.resume(throwing: _Concurrency.CancellationError())
52+
}
53+
}
54+
} onCancel: {
55+
let continuation = state.withLock {
56+
$0.isCancelled = true
57+
return $0.continuation
58+
}
59+
continuation?.resume(throwing: _Concurrency.CancellationError())
60+
}
61+
}
62+
}
63+
64+
#endif

Sources/Transferring.swift

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
//
2+
// Transferring.swift
3+
// swift-timeout
4+
//
5+
// Created by Simon Whitty on 02/06/2025.
6+
// Copyright 2025 Simon Whitty
7+
//
8+
// Distributed under the permissive MIT license
9+
// Get the latest version from here:
10+
//
11+
// https://github.com/swhitty/swift-timeout
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+
#if compiler(>=6.0)
33+
struct Transferring<Value>: Sendable {
34+
nonisolated(unsafe) public var value: Value
35+
init(_ value: Value) {
36+
self.value = value
37+
}
38+
}
39+
#else
40+
struct Transferring<Value>: @unchecked Sendable {
41+
var value: Value
42+
init(_ value: Value) {
43+
self.value = value
44+
}
45+
}
46+
#endif

Sources/withThrowingTimeout.swift

Lines changed: 1 addition & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -109,13 +109,8 @@ private func _withThrowingTimeout<T>(
109109
}
110110
}
111111

112-
private struct Transferring<Value>: Sendable {
113-
nonisolated(unsafe) public var value: Value
114-
init(_ value: Value) {
115-
self.value = value
116-
}
117-
}
118112
#else
113+
119114
public func withThrowingTimeout<T>(
120115
seconds: TimeInterval,
121116
body: () async throws -> T
@@ -178,10 +173,4 @@ private func _withThrowingTimeout<T: Sendable>(
178173
}
179174
}
180175

181-
private struct Transferring<Value>: @unchecked Sendable {
182-
var value: Value
183-
init(_ value: Value) {
184-
self.value = value
185-
}
186-
}
187176
#endif

Tests/MutexTests.swift

Lines changed: 76 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,76 @@
1+
//
2+
// MutexTests.swift
3+
// swift-mutex
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/swift-mutex
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+
#if canImport(Testing) && canImport(Darwin)
33+
@testable import Timeout
34+
import Testing
35+
36+
struct MutexTests {
37+
38+
@Test
39+
func withLock_ReturnsValue() {
40+
let mutex = Mutex("fish")
41+
let val = mutex.withLock {
42+
$0 + " & chips"
43+
}
44+
#expect(val == "fish & chips")
45+
}
46+
47+
@Test
48+
func withLock_ThrowsError() {
49+
let mutex = Mutex("fish")
50+
#expect(throws: CancellationError.self) {
51+
try mutex.withLock { _ -> Void in throw CancellationError() }
52+
}
53+
}
54+
55+
@Test
56+
func lockIfAvailable_ReturnsValue() {
57+
let mutex = Mutex("fish")
58+
mutex.storage.lock()
59+
#expect(
60+
mutex.withLockIfAvailable { _ in "chips" } == nil
61+
)
62+
mutex.storage.unlock()
63+
#expect(
64+
mutex.withLockIfAvailable { _ in "chips" } == "chips"
65+
)
66+
}
67+
68+
@Test
69+
func withLockIfAvailable_ThrowsError() {
70+
let mutex = Mutex("fish")
71+
#expect(throws: CancellationError.self) {
72+
try mutex.withLockIfAvailable { _ -> Void in throw CancellationError() }
73+
}
74+
}
75+
}
76+
#endif

0 commit comments

Comments
 (0)