Skip to content

Commit 0cf67e4

Browse files
authored
Merge pull request #74319 from ktoso/wip-global-custom-exec-docs
2 parents b14563f + fe66c47 commit 0cf67e4

File tree

3 files changed

+137
-3
lines changed

3 files changed

+137
-3
lines changed

stdlib/public/Concurrency/GlobalActor.swift

Lines changed: 34 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,25 @@ import Swift
2222
/// such a declaration from another actor (or from nonisolated code),
2323
/// synchronization is performed through the shared actor instance to ensure
2424
/// mutually-exclusive access to the declaration.
25+
///
26+
/// ## Custom Actor Executors
27+
/// A global actor use a custom executor if it needs to customize its execution
28+
/// semantics, for example, by making sure all of its invocations are run on a
29+
/// specific thread or dispatch queue.
30+
///
31+
/// This is done the same way as with normal non-global actors, by declaring a
32+
/// ``Actor/unownedExecutor`` nonisolated property in the ``ActorType``
33+
/// underlying this global actor.
34+
///
35+
/// It is *not* necessary to override the ``sharedUnownedExecutor`` static
36+
/// property of the global actor, as its default implementation already
37+
/// delegates to the ``shared.unownedExecutor``, which is the most reasonable
38+
/// and correct implementation of this protocol requirement.
39+
///
40+
/// You can find out more about custom executors, by referring to the
41+
/// ``SerialExecutor`` protocol's documentation.
42+
///
43+
/// - SeeAlso: ``SerialExecutor``
2544
@available(SwiftStdlib 5.1, *)
2645
public protocol GlobalActor {
2746
/// The type of the shared actor instance that will be used to provide
@@ -36,10 +55,22 @@ public protocol GlobalActor {
3655
/// instance.
3756
static var shared: ActorType { get }
3857

39-
/// The shared executor instance that will be used to provide
40-
/// mutually-exclusive access for the global actor.
58+
/// Shorthand for referring to the `shared.unownedExecutor` of this global actor.
59+
///
60+
/// When declaring a global actor with a custom executor, prefer to implement
61+
/// the underlying actor's ``Actor/unownedExecutor`` property, and leave this
62+
/// `sharedUnownedExecutor` default implementation in-place as it will simply
63+
/// delegate to the `shared.unownedExecutor`.
64+
///
65+
/// The value of this property must be equivalent to `shared.unownedExecutor`,
66+
/// as may be used by the Swift concurrency runtime or explicit user code. with
67+
/// that assumption in mind.
68+
///
69+
/// Returning different executors for different invocations of this computed
70+
/// property is also illegal, as it could lead to inconsistent synchronization
71+
/// of the underlying actor.
4172
///
42-
/// The value of this property must be equivalent to `shared.unownedExecutor`.
73+
/// - SeeAlso: ``SerialExecutor``
4374
static var sharedUnownedExecutor: UnownedSerialExecutor { get }
4475
}
4576

test/Concurrency/Runtime/custom_executors.swift

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,8 @@ actor Custom {
2727
}
2828

2929
func report() async {
30+
simple.preconditionIsolated() // we're supposed to be on the same executor as 'simple'
31+
3032
print("custom.count == \(count)")
3133
count += 1
3234

Lines changed: 101 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,101 @@
1+
// RUN: %target-run-simple-swift( %import-libdispatch -strict-concurrency=complete -parse-as-library) | %FileCheck %s
2+
3+
// REQUIRES: concurrency
4+
// REQUIRES: executable_test
5+
// REQUIRES: libdispatch
6+
// UNSUPPORTED: freestanding
7+
8+
// UNSUPPORTED: back_deployment_runtime
9+
// REQUIRES: concurrency_runtime
10+
11+
import Dispatch
12+
13+
let globalQueue = DispatchQueue(label: "SimpleQueue")
14+
15+
@available(SwiftStdlib 6.0, *)
16+
final class NaiveQueueExecutor: SerialExecutor {
17+
public func enqueue(_ unowned: UnownedJob) {
18+
globalQueue.sync {
19+
unowned.runSynchronously(on: self.asUnownedSerialExecutor())
20+
}
21+
}
22+
23+
public func asUnownedSerialExecutor() -> UnownedSerialExecutor {
24+
return UnownedSerialExecutor(ordinary: self)
25+
}
26+
27+
func checkIsolated() {
28+
// ok
29+
}
30+
}
31+
32+
@available(SwiftStdlib 6.0, *)
33+
actor Simple {
34+
var count = 0
35+
let exec = NaiveQueueExecutor()
36+
37+
func report() {
38+
print("simple.count == \(count)")
39+
count += 1
40+
}
41+
42+
nonisolated var unownedExecutor: UnownedSerialExecutor {
43+
print("Simple.unownedExecutor")
44+
return exec.asUnownedSerialExecutor()
45+
}
46+
}
47+
48+
@globalActor
49+
@available(SwiftStdlib 6.0, *)
50+
actor MyGlobalActor {
51+
static let simple = Simple()
52+
static let shared = MyGlobalActor()
53+
54+
static var sharedUnownedExecutor: UnownedSerialExecutor {
55+
print("MyGlobalActor.sharedUnownedExecutor")
56+
return simple.unownedExecutor
57+
}
58+
nonisolated var unownedExecutor: UnownedSerialExecutor {
59+
print("MyGlobalActor.unownedExecutor")
60+
return Self.simple.unownedExecutor
61+
}
62+
}
63+
64+
@MyGlobalActor
65+
@available(SwiftStdlib 6.0, *)
66+
final class Custom {
67+
var count = 0
68+
let simple = MyGlobalActor.simple
69+
70+
nonisolated var unownedExecutor: UnownedSerialExecutor {
71+
return simple.unownedExecutor
72+
}
73+
74+
func report() async {
75+
simple.preconditionIsolated()
76+
77+
print("custom.count == \(count)")
78+
count += 1
79+
80+
await simple.report()
81+
}
82+
}
83+
84+
@available(SwiftStdlib 6.0, *)
85+
@main struct Main {
86+
static func main() async {
87+
print("begin")
88+
let actor = Custom()
89+
await actor.report()
90+
print("end")
91+
}
92+
}
93+
94+
// CHECK: begin
95+
// CHECK-NEXT: MyGlobalActor.unownedExecutor
96+
// CHECK-NEXT: Simple.unownedExecutor
97+
// CHECK-NEXT: Simple.unownedExecutor
98+
// CHECK-NEXT: custom.count == 0
99+
// CHECK-NEXT: Simple.unownedExecutor
100+
// CHECK-NEXT: simple.count == 0
101+
// CHECK-NEXT: end

0 commit comments

Comments
 (0)