-
Notifications
You must be signed in to change notification settings - Fork 10.4k
/
Copy pathDistributedActor.swift
400 lines (369 loc) · 18 KB
/
DistributedActor.swift
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
//===----------------------------------------------------------------------===//
//
// This source file is part of the Swift.org open source project
//
// Copyright (c) 2020 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See https://swift.org/LICENSE.txt for license information
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
//
//===----------------------------------------------------------------------===//
import Swift
import _Concurrency
// ==== Distributed Actor -----------------------------------------------------
/// Common protocol to which all distributed actors conform implicitly.
///
/// The `DistributedActor` protocol generalizes over all distributed actor types.
/// Distributed actor types implicitly conform to this protocol.
///
/// It is not possible to conform to this protocol by any other declaration other than a `distributed actor`.
///
/// It is possible to require a type to conform to the
/// ``DistributedActor`` protocol by refining it with another protocol,
/// or by using a generic constraint.
///
/// ## Synthesized properties
/// For every concrete distributed actor declaration, the compiler synthesizes two properties: `actorSystem` and `id`.
/// They witness the ``actorSystem`` and ``id`` protocol requirements of the ``DistributedActor`` protocol.
///
/// It is not possible to implement these properties explicitly in user code.
/// These properties are `nonisolated` and accessible even if the instance is _remote_,
/// because _all_ distributed actor references must store the actor system remote calls
/// will be delivered through, as well as the id identifying the target of those calls.
///
/// ## The ActorSystem associated type
/// Every distributed actor must declare what type of actor system
/// it is part of by implementing the ``ActorSystem`` associated type requirement.
///
/// This causes a number of other properties of the actor to be inferred:
/// - the ``SerializationRequirement`` that will be used at compile time to
/// verify `distributed` target declarations are well formed,
/// - if the distributed actor is `Codable`, based on the ``ID`` being Codable or not,
/// - the type of the ``ActorSystem`` accepted in the synthesized default initializer.
///
/// A distributed actor must declare what type of actor system it is ready to
/// work with by fulfilling the ``ActorSystem`` type member requirement:
///
/// ```swift
/// distributed actor Greeter {
/// typealias ActorSystem = GreetingSystem // which conforms to DistributedActorSystem
///
/// func greet() -> String { "Hello!" }
/// }
/// ```
///
/// ### The DefaultDistributedActorSystem type alias
/// Since it is fairly common to only be using one specific type of actor system
/// within a module or entire codebase, it is possible to declare the default type
/// of actor system all distributed actors will be using in a module by declaring
/// a `DefaultDistributedActorSystem` module wide typealias:
///
/// ```swift
/// import Distributed
/// import AmazingActorSystemLibrary
///
/// typealias DefaultDistributedActorSystem = AmazingActorSystem
///
/// distributed actor Greeter {} // ActorSystem == AmazingActorSystem
/// ```
///
/// This declaration makes all `distributed actor` declarations
/// that do not explicitly specify an ``ActorSystem`` type alias to assume the
/// `AmazingActorSystem` as their `ActorSystem`.
///
/// It is possible for a specific actor to override the system it is using,
/// by declaring an ``ActorSystem`` type alias as usual:
///
/// ```swift
/// typealias DefaultDistributedActorSystem = AmazingActorSystem
///
/// distributed actor Amazing {
/// // ActorSystem == AmazingActorSystem
/// }
///
/// distributed actor Superb {
/// typealias ActorSystem = SuperbActorSystem
/// }
/// ```
///
/// In general the `DefaultDistributedActorSystem` should not be declared public,
/// as picking the default should be left up to each specific module of a project.
///
/// ## Default initializer
/// While classes and actors receive a synthesized *argument-free default
/// initializer* (`init()`), distributed actors synthesize a default initializer
/// that accepts a distributed actor system the actor is part of: `init(actorSystem:)`.
///
/// The accepted actor system must be of the `Self.ActorSystem` type, which
/// must conform to the ``DistributedActorSystem`` protocol. This is required
/// because distributed actors are always managed by a concrete
/// distributed actor system and cannot exist on their own without one.
///
/// It is possible to explicitly declare a parameter-free initializer (`init()`),
/// however the `actorSystem` property still must be assigned a concrete actor
/// system instance the actor shall be part of.
///
/// In general it is recommended to always have an `actorSystem` parameter as
/// the last non-defaulted non-closure parameter in every actor's
/// initializer parameter list. This way it is simple to swap in a "test actor
/// system" instance in unit tests, and avoid relying on global state which could
/// make testing more difficult.
///
/// ## Implicit properties
/// Every concrete `distributed actor` type receives two synthesized properties,
/// which implement the protocol requirements of this protocol: `id` and `actorSystem`.
///
/// ### Property: Actor System
/// The ``actorSystem`` property is an important part of every distributed actor's lifecycle management.
///
/// Both initialization as well as de-initialization require interactions with the actor system,
/// and it is the actor system that handles all remote interactions of an actor, by both sending
/// or receiving remote calls made on the actor.
///
/// The ``actorSystem`` property must be assigned in every designated initializer
/// of a distributed actor explicitly. It is highly recommended to make it a
/// parameter of every distributed actor initializer, and simply forward the
/// value to the stored property, like this:
///
/// ```swift
/// init(name: String, actorSystem: Self.ActorSystem) {
/// self.name = name
/// self.actorSystem = actorSystem
/// }
/// ```
///
/// Forgetting to initialize the actor system, will result in a compile time error:
///
/// ```swift
/// // BAD
/// init(name: String, actorSystem: Self.ActorSystem) {
/// self.name = name
/// // BAD, will cause compile-time error; the `actorSystem` was not initialized.
/// }
/// ```
///
/// ### Property: Distributed Actor Identity
/// ``id`` is assigned by the actor system during the distributed actor's
/// initialization, and cannot be set or mutated by the actor itself.
///
/// ``id`` is the effective identity of the actor, and is used in equality checks,
/// as well as the actor's synthesized ``Codable`` conformance if the ``ID`` type
/// conforms to ``Codable``.
///
/// ## Automatic Conformances
///
/// ### Hashable and Identifiable conformance
/// Every distributed actor conforms to the `Hashable` and `Identifiable` protocols.
/// Its identity is strictly driven by its ``id``, and therefore hash and equality
/// implementations directly delegate to the ``id`` property.
///
/// Comparing a local distributed actor instance and a remote reference to it
/// (both using the same ``id``) always returns true, as they both conceptually
/// point at the same distributed actor.
///
/// It is not possible to implement these protocols relying on the actual actor's
/// state, because it may be remote and the state may not be available. In other
/// words, since these protocols must be implemented using `nonisolated` functions,
/// only `nonisolated` `id` and `actorSystem` properties are accessible for their
/// implementations.
///
/// ### Implicit Codable conformance
/// If created with an actor system whose ``DistributedActorSystem/ActorID`` is ``Codable``, the
/// compiler will synthesize code for the concrete distributed actor to conform
/// to ``Codable`` as well.
///
/// This is necessary to support distributed calls where the `SerializationRequirement`
/// is ``Codable`` and thus users may want to pass actors as arguments to remote calls.
///
/// The synthesized implementations use a single ``SingleValueEncodingContainer`` to
/// encode/decode the ``id`` property of the actor. The ``Decoder`` required
/// ``Decoder/init(from:)`` is implemented by retrieving an actor system from the
/// decoders' `userInfo`, effectively like as follows:
///
/// ```swift
/// decoder.userInfo[.actorSystemKey] as? ActorSystem
// ```
///
/// The such obtained actor system is then used to ``resolve(id:using:)`` the decoded ``ID``.
///
/// Use the ``CodingUserInfoKey/actorSystemKey`` to provide the necessary
/// actor system for the decoding initializer when decoding a distributed actor.
///
/// - SeeAlso: ``DistributedActorSystem``
/// - SeeAlso: ``Actor``
/// - SeeAlso: ``AnyActor``
@available(SwiftStdlib 5.7, *)
public protocol DistributedActor: AnyActor, Identifiable, Hashable
where ID == ActorSystem.ActorID,
SerializationRequirement == ActorSystem.SerializationRequirement {
/// The type of transport used to communicate with actors of this type.
associatedtype ActorSystem: DistributedActorSystem
/// The serialization requirement to apply to all distributed declarations inside the actor.
associatedtype SerializationRequirement
/// Logical identity of this distributed actor.
///
/// Many distributed actor references may be pointing at, logically, the same actor.
/// For example, calling `resolve(id:using:)` multiple times, is not guaranteed
/// to return the same exact resolved actor instance, however all the references would
/// represent logically references to the same distributed actor, e.g. on a different node.
///
/// Depending on the capabilities of the actor system producing the identifiers,
/// the `ID` may also be used to store instance specific metadata.
///
/// ## Synthesized property
///
/// In concrete distributed actor declarations, a witness for this protocol requirement is synthesized by the compiler.
///
/// It is not possible to assign a value to the `id` directly; instead, it is assigned during an actors `init` (or `resolve`),
/// by the managing actor system.
nonisolated override var id: ID { get }
/// The ``DistributedActorSystem`` that is managing this distributed actor.
///
/// It is immutable and equal to the system assigned during the distributed actor's local initializer
/// (or to the system passed to the ``resolve(id:using:)`` static function).
///
/// ## Synthesized property
///
/// In concrete distributed actor declarations, a witness for this protocol requirement is synthesized by the compiler.
///
/// It is required to assign an initial value to the `actorSystem` property inside a distributed actor's designated initializer.
/// Semantically, it can be treated as a `let` declaration, that must be assigned in order to fully-initialize the instance.
///
/// If a distributed actor declares no initializer, its default initializer will take the shape of `init(actorSystem:)`,
/// and initialize this property using the passed ``DistributedActorSystem``. If any user-defined initializer exists,
/// the default initializer is not synthesized, and all the user-defined initializers must take care to initialize this property.
nonisolated var actorSystem: ActorSystem { get }
/// Retrieve the executor for this distributed actor as an optimized,
/// unowned reference. This API is equivalent to ``Actor/unownedExecutor``,
/// however, by default, it intentionally returns `nil` if this actor is a reference
/// to a remote distributed actor, because the executor for remote references
/// is effectively never g
///
/// ## Custom implementation requirements
///
/// This property must always evaluate to the same executor for a
/// given actor instance, and holding on to the actor must keep the
/// executor alive.
///
/// This property will be implicitly accessed when work needs to be
/// scheduled onto this actor. These accesses may be merged,
/// eliminated, and rearranged with other work, and they may even
/// be introduced when not strictly required. Visible side effects
/// are therefore strongly discouraged within this property.
@available(SwiftStdlib 5.9, *)
nonisolated var unownedExecutor: UnownedSerialExecutor{ get }
/// Resolves the passed in `id` against the `system`, returning
/// either a local or remote actor reference.
///
/// The system will be asked to `resolve` the identity and return either
/// a local instance or request a proxy to be created for this identity.
///
/// A remote distributed actor reference will forward all invocations through
/// the system, allowing it to take over the remote messaging with the
/// remote actor instance.
///
/// - Postcondition: upon successful return, the returned actor's ``id`` and ``actorSystem`` properties
/// will be equal to the values passed as parameters to this method.
/// - Parameter id: identity uniquely identifying a, potentially remote, actor in the system
/// - Parameter system: `system` which should be used to resolve the `identity`, and be associated with the returned actor
static func resolve(id: ID, using system: ActorSystem) throws -> Self
}
// ==== Hashable conformance ---------------------------------------------------
@available(SwiftStdlib 5.7, *)
extension DistributedActor {
/// A distributed actor's hash and equality is implemented by directly delegating to its ``id``.
///
/// For more details see the "Hashable and Identifiable conformance" section of ``DistributedActor``.
nonisolated public func hash(into hasher: inout Hasher) {
self.id.hash(into: &hasher)
}
/// A distributed actor's hash and equality is implemented by directly delegating to its ``id``.
///
/// For more details see the "Hashable and Identifiable conformance" section of ``DistributedActor``.
nonisolated public static func ==(lhs: Self, rhs: Self) -> Bool {
lhs.id == rhs.id
}
}
// ==== Codable conformance ----------------------------------------------------
extension CodingUserInfoKey {
/// Key which is required to be set on a `Decoder`'s `userInfo` while attempting
/// to `init(from:)` a `DistributedActor`. The stored value under this key must
/// conform to ``DistributedActorSystem``.
///
/// Forgetting to set this key will result in that initializer throwing, because
/// an actor system is required in order to call ``DistributedActor/resolve(id:using:)`` using it.
@available(SwiftStdlib 5.7, *)
public static let actorSystemKey = CodingUserInfoKey(rawValue: "$distributed_actor_system")!
}
@available(SwiftStdlib 5.7, *)
extension DistributedActor /*: implicitly Decodable */ where Self.ID: Decodable {
/// Initializes an instance of this distributed actor by decoding its ``id``,
/// and passing it to the ``DistributedActorSystem`` obtained from `decoder.userInfo[actorSystemKey]`.
///
/// ## Requires: The decoder must have the ``CodingUserInfoKey/actorSystemKey`` set to
/// the ``ActorSystem`` that this actor expects, as it will be used to call ``DistributedActor/resolve(id:using:)``
/// on, in order to obtain the instance this initializer should return.
///
/// - Parameter decoder: used to decode the ``ID`` of this distributed actor.
/// - Throws: If the actor system value in `decoder.userInfo` is missing or mis-typed;
/// the `ID` fails to decode from the passed `decoder`;
// or if the ``DistributedActor/resolve(id:using:)`` method invoked by this initializer throws.
nonisolated public init(from decoder: Decoder) throws {
guard let system = decoder.userInfo[.actorSystemKey] as? ActorSystem else {
throw DistributedActorCodingError(message:
"Missing system (for key .actorSystemKey) " +
"in Decoder.userInfo, while decoding \(Self.self).")
}
let id: ID = try Self.ID(from: decoder)
self = try Self.resolve(id: id, using: system)
}
}
@available(SwiftStdlib 5.7, *)
extension DistributedActor /*: implicitly Encodable */ where Self.ID: Encodable {
/// Encodes the `actor.id` as a single value into the passed `encoder`.
nonisolated public func encode(to encoder: Encoder) throws {
var container = encoder.singleValueContainer()
try container.encode(self.id)
}
}
// ==== Local actor special handling -------------------------------------------
@available(SwiftStdlib 5.7, *)
extension DistributedActor {
/// Executes the passed 'body' only when the distributed actor is local instance.
///
/// The `Self` passed to the body closure is isolated, meaning that the
/// closure can be used to call non-distributed functions, or even access actor
/// state.
///
/// When the actor is remote, the closure won't be executed and this function will return nil.
public nonisolated func whenLocal<T: Sendable>(
_ body: @Sendable (isolated Self) async throws -> T
) async rethrows -> T? {
if __isLocalActor(self) {
return try await body(self)
} else {
return nil
}
}
}
/******************************************************************************/
/************************* Runtime Functions **********************************/
/******************************************************************************/
// ==== isRemote / isLocal -----------------------------------------------------
/// Verifies if the passed ``DistributedActor`` conforming type is a remote reference.
/// Passing a type not conforming to ``DistributedActor`` may result in undefined behavior.
///
/// Official API to perform this task is `whenLocal`.
@_silgen_name("swift_distributed_actor_is_remote")
public func __isRemoteActor(_ actor: AnyObject) -> Bool
/// Verifies if the passed ``DistributedActor`` conforming type is a local reference.
/// Passing a type not conforming to ``DistributedActor`` may result in undefined behavior.
///
/// Official API to perform this task is `whenLocal`.
public func __isLocalActor(_ actor: AnyObject) -> Bool {
return !__isRemoteActor(actor)
}
// ==== Proxy Actor lifecycle --------------------------------------------------
@_silgen_name("swift_distributedActor_remote_initialize")
func _distributedActorRemoteInitialize(_ actorType: Builtin.RawPointer) -> Any