Skip to content

Commit 7cfc1b9

Browse files
authored
Merge pull request #81063 from ktoso/wip-actor.withSerialExecutor
[Concurrency] Offer way to get SerialExecutor from Actor
2 parents 68869c6 + 7112f7e commit 7cfc1b9

File tree

3 files changed

+134
-2
lines changed

3 files changed

+134
-2
lines changed

stdlib/public/Concurrency/Executor.swift

+25
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: ~Copyable, E: Error>(_ operation: (any SerialExecutor) throws(E) -> T) throws(E) -> 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: ~Copyable, E: Error>(_ operation: nonisolated(nonsending) (any SerialExecutor) async throws(E) -> T) async throws(E) -> 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.
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+
}

test/IDE/complete_cache_notrecommended.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -32,11 +32,13 @@ func testAsync() async -> Int {
3232
}
3333
func testSyncMember(obj: MyActor) -> Int {
3434
obj.#^MEMBER_IN_SYNC^#
35-
// MEMBER_IN_SYNC: Begin completions, 9 items
35+
// MEMBER_IN_SYNC: Begin completions, 11 items
3636
// MEMBER_IN_SYNC-DAG: Keyword[self]/CurrNominal: self[#MyActor#];
3737
// MEMBER_IN_SYNC-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Convertible]: actorMethod()[' async'][#Int#];
3838
// MEMBER_IN_SYNC-DAG: Decl[InstanceMethod]/CurrNominal/NotRecommended: deprecatedMethod()[' async'][#Void#];
3939
// MEMBER_IN_SYNC-DAG: Decl[InstanceVar]/CurrNominal: unownedExecutor[#UnownedSerialExecutor#];
40+
// MEMBER_IN_SYNC-DAG: Decl[InstanceMethod]/Super/IsSystem: withSerialExecutor({#(operation): (any SerialExecutor) throws(Error) -> ~Copyable##(any SerialExecutor) throws(Error) -> ~Copyable#})[' throws'][#~Copyable#]; name=withSerialExecutor(:)
41+
// MEMBER_IN_SYNC-DAG: Decl[InstanceMethod]/Super/IsSystem: withSerialExecutor({#(operation): (any SerialExecutor) async throws(Error) -> ~Copyable##(any SerialExecutor) async throws(Error) -> ~Copyable#})[' async'][' throws'][#~Copyable#]; name=withSerialExecutor(:)
4042
// MEMBER_IN_SYNC-DAG: Decl[InstanceMethod]/Super/IsSystem: preconditionIsolated()[#Void#]; name=preconditionIsolated()
4143
// MEMBER_IN_SYNC-DAG: Decl[InstanceMethod]/Super/IsSystem: preconditionIsolated({#(message): String#})[#Void#]; name=preconditionIsolated(:)
4244
// MEMBER_IN_SYNC-DAG: Decl[InstanceMethod]/Super/IsSystem: assertIsolated()[#Void#]; name=assertIsolated()
@@ -46,11 +48,13 @@ func testSyncMember(obj: MyActor) -> Int {
4648

4749
func testSyncMember(obj: MyActor) async -> Int {
4850
obj.#^MEMBER_IN_ASYNC^#
49-
// MEMBER_IN_ASYNC: Begin completions, 9 items
51+
// MEMBER_IN_ASYNC: Begin completions, 11 items
5052
// MEMBER_IN_ASYNC-DAG: Keyword[self]/CurrNominal: self[#MyActor#];
5153
// MEMBER_IN_ASYNC-DAG: Decl[InstanceMethod]/CurrNominal/TypeRelation[Convertible]: actorMethod()[' async'][#Int#];
5254
// MEMBER_IN_ASYNC-DAG: Decl[InstanceMethod]/CurrNominal/NotRecommended: deprecatedMethod()[' async'][#Void#];
5355
// MEMBER_IN_ASYNC-DAG: Decl[InstanceVar]/CurrNominal: unownedExecutor[#UnownedSerialExecutor#];
56+
// MEMBER_IN_ASYNC-DAG: Decl[InstanceMethod]/Super/IsSystem: withSerialExecutor({#(operation): (any SerialExecutor) throws(Error) -> ~Copyable##(any SerialExecutor) throws(Error) -> ~Copyable#})[' throws'][#~Copyable#]; name=withSerialExecutor(:)
57+
// MEMBER_IN_ASYNC-DAG: Decl[InstanceMethod]/Super/IsSystem: withSerialExecutor({#(operation): (any SerialExecutor) async throws(Error) -> ~Copyable##(any SerialExecutor) async throws(Error) -> ~Copyable#})[' async'][' throws'][#~Copyable#]; name=withSerialExecutor(:)
5458
// MEMBER_IN_ASYNC-DAG: Decl[InstanceMethod]/Super/IsSystem: preconditionIsolated()[#Void#]; name=preconditionIsolated()
5559
// MEMBER_IN_ASYNC-DAG: Decl[InstanceMethod]/Super/IsSystem: preconditionIsolated({#(message): String#})[#Void#]; name=preconditionIsolated(:)
5660
// MEMBER_IN_ASYNC-DAG: Decl[InstanceMethod]/Super/IsSystem: assertIsolated()[#Void#]; name=assertIsolated()

0 commit comments

Comments
 (0)