Skip to content

Commit 20fe3b0

Browse files
committed
[Concurrency] Offer way to get SerialExecutor from Actor
1 parent cfe6195 commit 20fe3b0

File tree

2 files changed

+128
-0
lines changed

2 files changed

+128
-0
lines changed

stdlib/public/Concurrency/Executor.swift

Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -93,6 +93,31 @@ public protocol SchedulableExecutor: Executor {
9393

9494
}
9595

96+
extension Actor {
97+
98+
/// Perform an operation with the actor's ``SerialExecutor``.
99+
///
100+
/// This converts the actor's ``Actor/unownedExecutor`` to a ``SerialExecutor`` while
101+
/// retaining the actor for the duration of the operation. This is to ensure the lifetime
102+
/// of the executor while performing the operation.
103+
@_alwaysEmitIntoClient
104+
@available(SwiftStdlib 5.1, *)
105+
public nonisolated func withSerialExecutor<T>(_ operation: (any SerialExecutor) throws -> T) rethrows -> T {
106+
try operation(unsafe unsafeBitCast(self.unownedExecutor, to: (any SerialExecutor).self))
107+
}
108+
109+
/// Perform an operation with the actor's ``SerialExecutor``.
110+
///
111+
/// This converts the actor's ``Actor/unownedExecutor`` to a ``SerialExecutor`` while
112+
/// retaining the actor for the duration of the operation. This is to ensure the lifetime
113+
/// of the executor while performing the operation.
114+
@_alwaysEmitIntoClient
115+
@available(SwiftStdlib 5.1, *)
116+
public nonisolated func withSerialExecutor<T>(_ operation: (any SerialExecutor) async throws -> T) async rethrows -> T {
117+
try await operation(unsafe unsafeBitCast(self.unownedExecutor, to: (any SerialExecutor).self))
118+
}
119+
}
120+
96121
extension Executor {
97122
/// Return this executable as a SchedulableExecutor, or nil if that is
98123
/// unsupported.
Lines changed: 103 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,103 @@
1+
// RUN: %empty-directory(%t)
2+
// RUN: %target-build-swift %import-libdispatch -Xfrontend -disable-availability-checking -parse-as-library %s -o %t/a.out
3+
// RUN: %target-codesign %t/a.out
4+
// RUN: %target-run %t/a.out | %FileCheck %s
5+
6+
// REQUIRES: executable_test
7+
// REQUIRES: concurrency
8+
// REQUIRES: concurrency_runtime
9+
10+
// REQUIRES: libdispatch
11+
12+
// UNSUPPORTED: back_deployment_runtime
13+
// UNSUPPORTED: back_deploy_concurrency
14+
// UNSUPPORTED: use_os_stdlib
15+
// UNSUPPORTED: freestanding
16+
17+
@available(SwiftStdlib 6.2, *)
18+
final class IsIsolatingExecutor: SerialExecutor {
19+
init() {}
20+
21+
func enqueue(_ job: consuming ExecutorJob) {
22+
job.runSynchronously(on: self.asUnownedSerialExecutor())
23+
}
24+
25+
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
26+
UnownedSerialExecutor(ordinary: self)
27+
}
28+
29+
func checkIsolated() {
30+
print("called: checkIsolated")
31+
}
32+
33+
func isIsolatingCurrentContext() -> Bool {
34+
print("called: isIsolatingCurrentContext")
35+
return true
36+
}
37+
}
38+
39+
@available(SwiftStdlib 6.2, *)
40+
final class NoChecksImplementedExecutor: SerialExecutor {
41+
init() {}
42+
43+
func enqueue(_ job: consuming ExecutorJob) {
44+
job.runSynchronously(on: self.asUnownedSerialExecutor())
45+
}
46+
47+
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
48+
UnownedSerialExecutor(ordinary: self)
49+
}
50+
}
51+
52+
@available(SwiftStdlib 6.2, *)
53+
final class JustCheckIsolatedExecutor: SerialExecutor {
54+
init() {}
55+
56+
func enqueue(_ job: consuming ExecutorJob) {
57+
job.runSynchronously(on: self.asUnownedSerialExecutor())
58+
}
59+
60+
func asUnownedSerialExecutor() -> UnownedSerialExecutor {
61+
UnownedSerialExecutor(ordinary: self)
62+
}
63+
64+
func checkIsolated() {
65+
print("called: checkIsolated")
66+
}
67+
}
68+
69+
@available(SwiftStdlib 6.2, *)
70+
actor ActorOnIsCheckImplementingExecutor<Ex: SerialExecutor> {
71+
let executor: Ex
72+
73+
init(on executor: Ex) {
74+
self.executor = executor
75+
}
76+
77+
nonisolated var unownedExecutor: UnownedSerialExecutor {
78+
self.executor.asUnownedSerialExecutor()
79+
}
80+
81+
func checkIsIsolatingCurrentContext() async -> Bool {
82+
executor.isIsolatingCurrentContext()
83+
}
84+
}
85+
86+
@main struct Main {
87+
static func main() async {
88+
let hasIsIsolatingCurrentContextExecutor = IsIsolatingExecutor()
89+
let hasIsCheckActor = ActorOnIsCheckImplementingExecutor(on: hasIsIsolatingCurrentContextExecutor)
90+
91+
let anyActor: any Actor = hasIsCheckActor
92+
93+
anyActor.withSerialExecutor { se in
94+
let outside = se.isIsolatingCurrentContext()
95+
assert(outside == true) // This is just a mock executor impl that always returns "true" (it is lying)
96+
// CHECK: called: isIsolatingCurrentContext
97+
}
98+
99+
let inside = await hasIsCheckActor.checkIsIsolatingCurrentContext()
100+
assert(inside == true)
101+
// CHECK: called: isIsolatingCurrentContext
102+
}
103+
}

0 commit comments

Comments
 (0)