Skip to content

Commit 9fa8866

Browse files
chore(patch): [sc-24600] use Task.immediate for runSync if possible (#71)
* chore(patch): [sc-24600] use Task.immediate for runSync if possible * resolve some lints by the way * create closure before condition * revert sendability to avoid API break * skip test different way
1 parent d75bbad commit 9fa8866

File tree

7 files changed

+52
-55
lines changed

7 files changed

+52
-55
lines changed

Sources/ConcurrencyHelpers/Lock.swift

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@
1717
#error("Unsupported Platform")
1818
#endif
1919

20-
public final class Lock {
20+
public final class Lock: @unchecked Sendable {
2121
#if os(macOS)
2222
fileprivate let mutex = UnsafeMutablePointer<os_unfair_lock>.allocate(capacity: 1)
2323
#else
@@ -76,7 +76,3 @@ public final class Lock {
7676
}
7777

7878
extension Lock: Lockable {}
79-
80-
#if compiler(>=5.5) && canImport(_Concurrency)
81-
extension Lock: Sendable {}
82-
#endif
Lines changed: 4 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
//===----------------------------------------------------------------------===//
1+
// ===----------------------------------------------------------------------=== //
22
//
33
// This source file is part of the SwiftNIO open source project
44
//
@@ -10,29 +10,15 @@
1010
//
1111
// SPDX-License-Identifier: Apache-2.0
1212
//
13-
//===----------------------------------------------------------------------===//
13+
// ===----------------------------------------------------------------------=== //
1414

1515
// Adopted from SwiftNIO for boxing values between concurrency domains when needed
1616

17-
/*
18-
#if swift(>=5.5) && canImport(_Concurrency)
19-
public typealias NIOSendable = Swift.Sendable
20-
#else
21-
public typealias NIOSendable = Any
22-
#endif
23-
24-
#if swift(>=5.6)
25-
@preconcurrency public protocol NIOPreconcurrencySendable: Sendable {}
26-
#else
27-
public protocol NIOPreconcurrencySendable {}
28-
#endif
29-
*/
30-
3117
/// ``UnsafeTransfer`` can be used to make non-`Sendable` values `Sendable`.
3218
/// As the name implies, the usage of this is unsafe because it disables the sendable checking of the compiler.
3319
/// It can be used similar to `@unsafe Sendable` but for values instead of types.
3420

35-
public struct UnsafeTransfer<Wrapped> {
21+
public struct UnsafeTransfer<Wrapped>: @unchecked Sendable {
3622
public var wrappedValue: Wrapped
3723

3824
@inlinable
@@ -41,25 +27,17 @@ public struct UnsafeTransfer<Wrapped> {
4127
}
4228
}
4329

44-
#if swift(>=5.5) && canImport(_Concurrency)
45-
extension UnsafeTransfer: @unchecked Sendable {}
46-
#endif
47-
4830
extension UnsafeTransfer: Equatable where Wrapped: Equatable {}
4931
extension UnsafeTransfer: Hashable where Wrapped: Hashable {}
5032

5133
/// ``UnsafeMutableTransferBox`` can be used to make non-`Sendable` values `Sendable` and mutable.
5234
/// It can be used to capture local mutable values in a `@Sendable` closure and mutate them from within the closure.
5335
/// As the name implies, the usage of this is unsafe because it disables the sendable checking of the compiler and does not add any synchronisation.
54-
public final class UnsafeMutableTransferBox<Wrapped> {
36+
public final class UnsafeMutableTransferBox<Wrapped>: @unchecked Sendable {
5537
public var wrappedValue: Wrapped
5638

5739
@inlinable
5840
public init(_ wrappedValue: Wrapped) {
5941
self.wrappedValue = wrappedValue
6042
}
6143
}
62-
63-
#if swift(>=5.5) && canImport(_Concurrency)
64-
extension UnsafeMutableTransferBox: @unchecked Sendable {}
65-
#endif

Sources/ConcurrencyHelpers/RunSync.swift

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ public func runSync<T>(priority: TaskPriority? = nil, _ operation: () async -> T
2323

2424
let wrapper = UnsafeMutableTransferBox(Optional(escapableOperation))
2525

26-
Task(priority: priority) { [wrapper] in
26+
let body = { [wrapper] in
2727
result.wrappedValue = await wrapper.wrappedValue!()
2828

2929
// Make sure reference to escapableOperation is released here otherwise
@@ -32,7 +32,18 @@ public func runSync<T>(priority: TaskPriority? = nil, _ operation: () async -> T
3232

3333
semaphore.signal()
3434
}
35-
35+
#if compiler(>=6.2)
36+
// preferably to have
37+
// if #compiler (>=6.2) && #available(macOS 26, iOS 26, *)
38+
// but that is not supported by swift...
39+
if #available(macOS 26, iOS 26, *) {
40+
Task.immediate(priority: priority, operation: body)
41+
} else {
42+
Task(priority: priority, operation: body)
43+
}
44+
#else
45+
Task(priority: priority, operation: body)
46+
#endif
3647
semaphore.wait()
3748
}
3849

Sources/ConcurrencyHelpers/Spinlock.swift

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -6,11 +6,11 @@
66
//
77
// http://www.apache.org/licenses/LICENSE-2.0
88

9-
import Atomics
109
import _PauseShims
10+
import Atomics
1111

1212
/// Lock to protect very short critical sections.
13-
public final class Spinlock {
13+
public final class Spinlock: @unchecked Sendable {
1414
private typealias State = Bool
1515

1616
private static let locked: State = true
@@ -52,7 +52,3 @@ public final class Spinlock {
5252
}
5353

5454
extension Spinlock: Lockable {}
55-
56-
#if compiler(>=5.5) && canImport(_Concurrency)
57-
extension Spinlock: @unchecked Sendable {}
58-
#endif

Sources/ConcurrencyHelpers/YieldWithBackPressure.swift

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,10 @@
1616
/// - Parameter continuation: The continuation to yield message to.
1717
/// - Returns: `true` if the `message` has been succesfully yielded to the stream and `false` in otherwise.
1818
@discardableResult
19-
public func yieldWithBackPressure<Message>(message: Message,
20-
to continuation: AsyncStream<Message>.Continuation) async -> Bool {
19+
public func yieldWithBackPressure<Message>(
20+
message: Message,
21+
to continuation: AsyncStream<Message>.Continuation
22+
) async -> Bool {
2123
while true {
2224
let result = continuation.yield(message)
2325
switch result {

Tests/ConcurrencyHelpersTests/ConcurrencyHelpersTests.swift

Lines changed: 28 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -34,11 +34,11 @@ final class ConcurrencyHelpersTests: XCTestCase {
3434
await doTestLockable(Lock.self)
3535
}
3636

37-
struct Data {
37+
private struct Data {
3838
@Protected var valueA: Int = 1
39-
@Protected var valueB: Int? = nil
39+
@Protected var valueB: Int?
4040

41-
public mutating func setA(_ a: Int) {
41+
mutating func setA(_ a: Int) {
4242
_valueA.write {
4343
$0 = a
4444
}
@@ -77,7 +77,7 @@ final class ConcurrencyHelpersTests: XCTestCase {
7777
await doTestLockable(Spinlock.self)
7878
}
7979

80-
private func doTestLockable<Mutex: Lockable>(_: Mutex.Type) async {
80+
private func doTestLockable<Mutex: Lockable & Sendable>(_: Mutex.Type) async {
8181
let taskCount = 10
8282
let iterationCount = 10_000
8383

@@ -108,6 +108,28 @@ final class ConcurrencyHelpersTests: XCTestCase {
108108
XCTAssertEqual(result, 34 * 2)
109109
}
110110

111+
func testRunSyncInRunSync() throws {
112+
#if !compiler(>=6.2)
113+
throw XCTSkip("Skipping test: compiler version does not support immediate tasks")
114+
#endif
115+
guard #available(macOS 26, iOS 26, *) else {
116+
throw XCTSkip("Skipping test: OS version does not support immediate tasks")
117+
}
118+
119+
func runSyncRec(recursion: Int) -> Int {
120+
if recursion <= 0 {
121+
return runSync {
122+
await self.someAsyncMethod(argument: 34)
123+
}
124+
}
125+
return runSync {
126+
runSyncRec(recursion: recursion - 1)
127+
}
128+
}
129+
let result = runSyncRec(recursion: 100)
130+
XCTAssertEqual(result, 34 * 2)
131+
}
132+
111133
func testRunSyncWithPriority() {
112134
let result = runSync(priority: .userInitiated) { await self.someAsyncMethod(argument: 34) }
113135
XCTAssertEqual(result, 34 * 2)
@@ -155,7 +177,7 @@ final class ConcurrencyHelpersTests: XCTestCase {
155177
print("Never")
156178
}
157179
}
158-
180+
159181
wrapper.exception = exception
160182
}
161183
let exception = wrapper.exception
@@ -191,7 +213,7 @@ final class ConcurrencyHelpersTests: XCTestCase {
191213
}
192214
}
193215

194-
extension AsyncStream.Continuation.YieldResult: Equatable where Element: Equatable {
216+
extension AsyncStream.Continuation.YieldResult: @retroactive Equatable where Element: Equatable {
195217
public static func == (lhs: Self, rhs: Self) -> Bool {
196218
switch lhs {
197219
case .enqueued(let lhsRemaining):

Tests/HelpersTests/TimeIntervalCounterTests.swift

Lines changed: 0 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -10,14 +10,6 @@
1010
import XCTest
1111

1212
final class TimeIntervalCounterTests: XCTestCase {
13-
override func setUp() {
14-
super.setUp()
15-
}
16-
17-
override func tearDown() {
18-
super.tearDown()
19-
}
20-
2113
private func counterTest(forIterations iterations: UInt64, interval: Duration) {
2214
var counter = TimeIntervalCounter(clock: ContinuousClock(), timeInterval: interval)
2315
var checkPointExecuted = false

0 commit comments

Comments
 (0)