//===----------------------------------------------------------------------===// // // This source file is part of the Swift.org open source project // // Copyright (c) 2020-2021 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 /// A distributed actor system underpins and implements all functionality of distributed actors. /// /// A ``DistributedActor`` is always initialized in association with some concrete actor system. /// That actor system instance is then used to manage the identity of the actor, as well as /// handle all remote interactions of the distributed actor. /// /// ## Using a DistributedActorSystem library /// /// From a library user's perspective (e.g. someone using a `ClusterSystem` or `SampleWebSocketActorSystem`), /// the basic use of a distributed actor system is fairly opaque. /// /// Any distributed actor must declare what actor system it is able to operate with. This is done either by a /// `typealias ActorSystem` in the body of such `distributed actor` declaration, or a module-wide global /// `typealias DefaultDistributedActorSystem`. Refer to the ``DistributedActor`` documentation to learn more /// about the tradeoffs of these approaches. /// /// Once an actor has declared the system it is able to work with, an instance of the system must be provided /// at initialization time, in order for the system to be able to take over the actor's identity management. /// /// For example, a simple distributed actor may look like this: /// /// ```swift /// distributed actor Greeter { /// init(name: String, actorSystem: ActorSystem) { /// self.name = name /// self.actorSystem = actorSystem // required (!) initialization of implicit actorSystem property /// } /// } /// ``` /// Notice that every distributed actor initializer must initialize the synthesized ``DistributedActor/actorSystem``. /// This property is later used for identity management and other remote interactions of the actor. /// For more details refer to ``DistributedActor`` which explains more about declaring distributed actors. /// /// For more details about how the specific actor system implementation deals with remote message transports /// and serialization, please refer to the specific system's documentation. /// /// > Note: For example, you may refer to the [Swift Distributed Actors cluster library](https://github.com/apple/swift-distributed-actors/) documentation, /// > which is one example of such feature complete distributed actor system implementation. /// /// ## Implementing a DistributedActorSystem /// /// This section is dedicated to distributed actor system library authors, and generally can be skipped over /// by library users, as it explains the interactions of synthesized code and specific distributed actor system methods /// and how they must be implemented. /// /// Methods discussed in this section are generally not intended to be called directly, but instead will have calls /// generated to them from distributed actor declarations in appropriate places (such as initializers, `distributed func` calls, or `distributed` computed properties). /// /// ### Assigning and Resigning Actor Identifiers /// /// During a local distributed actor's initialization (i.e. any `init` of a `distributed actor`), the actor system will /// be invoked in order to assign an ``ActorID`` for this actor. /// /// A call to ``assignID(_:)`` is made during the initialization of the distributed actor. /// The snippet below showcases this, though no guarantees are made at this point about the exact placement of this call. /// /// ```swift /// distributed actor ShowcaseIDInit { /// // let actorSystem: ActorSystem // synthesized; /// /// // typealias ID = ActorSystem.ActorID /// // let id: ID // synthesized; implements `Identifiable.id` requirement /// /// init(actorSystem: ActorSystem) { /// self.actorSystem = actorSystem /// // ... /// // self.id = actorSystem.assignID(Self.self) // synthesized; /// // ... /// } /// } /// ``` /// /// The result of ``assignID(_:)`` is then directly stored in the synthesized `id` property of the actor. /// /// The actor system should assign _globally unique_ identifiers to types, such that they may be properly resolved /// from any process in the distributed actor system. The exact shape of the ``ActorID`` is left up to the library to decide. /// It can be as small as an integer based identifier, or as large as a series of key-value pairs identifying the actor. /// /// The actor system must retain a mapping from the ``ActorID`` to the specific actor _instance_ which it is given in /// ``actorReady(_:)`` in order to implement the ``resolve(id:using:)`` method, which is how incoming and outgoing remote calls are made possible. /// /// Users have no control over this assignment, nor are they allowed to set the `id` property explicitly. /// The ``DistributedActor/id`` is used to implement the distributed actor's ``Hashable``, ``Equatable``, /// and even ``Codable`` conformance (which is synthesized if and only if the ``ActorID`` is ``Codable`` itself). /// /// > Tip: Take note that throwing or failable initializers complicate this somewhat. Thankfully, the compiler /// > will always emit the right code such that every ``assignID(_:)`` is balanced with a ``resignID(_:)`` call, /// > when the actor either failed to initialize or deinitialize properly. /// > /// > It is also possible that a throwing initializer throws before assigning the `actorSystem` and `id` properties. /// > In such case, no `assignID` nor `resignID` calls are made. There is no risk of the compiler ever attempting /// > to call a `resignID(_:)` without first having assigned given ID. /// /// Manually invoking `assignID` and `resignID` is generally not recommended but isn't strictly a programmer error, /// and it is up to the actor system to decide how to deal with such calls. /// /// Once the ``distributed actor`` deinitializes, a call to ``resignID(_:)`` will be made. Generally this is made from /// the distributed actor's `deinit`, however in the case of throwing initializers it may also happen during such failed /// init, in order to release the ID that is no longer used. /// /// ```swift /// // Synthesized inside a distributed actor's deinit: /// deinit { /// // actorSystem.resignID(self.id) /// } /// ``` /// /// After an ID is resigned, it technically could be used to identify another instance. /// For example, an advanced actor system implementation could use such approach to implement actors which /// are created "ad-hoc" and always contain the appropriate ID, and if one isn't allocated yet for such ID, /// it could _then_ create one on demand and make sure it is assigned the required ID. /// /// ### Readying Distributed Actors /// /// Once a `distributed actor` has been _fully initialized_ during its initializer, a call to ``actorReady(_:)`` /// is synthesized. This call is made after the actor's properties (including all user-declared properties) have been /// initialized, but before other user-defined code in the initializer gets a chance to run. /// /// > Note: Generally due to actor initializer isolation rules, users will need to make their initializers `async` /// in order to write code that safely performs extra actions after it has fully initialized. /// /// The ``actorReady(_)`` call on the actor system is a signal to the actor system that this actor _instance_ is now ready /// and may be resolved and interacted with via the actor system. Generally, a distributed actor system implementation /// will _weakly retain_ the actors it has readied, because retaining them strongly would mean that they will never be /// deallocated (and thus never resign their ID's). /// /// > Note: Generally actor systems should retain actors _weakly_ in order to allow them be deinitialized when no longer in use. /// > /// > Sometimes though, it can be quite useful to have the system retain certain "well known" actors, for example when it is expected /// > that other nodes in the distributed system will need to interact with them, even if end-user code no longer holds /// > strong references to them. An example of such "retain while actor system is active" distributed actors would be any kind /// > of actor which implements discovery or health check mechanisms between clustered nodes, sometimes called "system actors", /// > i.e. actors that serve the actor system directly. /// /// Next, we will discuss the just mentioned `resolve` method, which is closely tied to readying actors. /// /// ### Resolving (potentially remote) Distributed Actors /// /// An important aspect of any distributed actor system is being able to turn a ``DistributedActor`` type and ``ActorID`` /// into a reference to an actor (instance), regardless where the actor is located. The ID should have enough information stored /// to be able to make the decision of _where_ the actor is located, without having to contact remote nodes. Specifically, /// the implementation of ``DistributedActorSystem/resolve(id:as:)`` is _not_ `async` and should _not_ perform long running /// or blocking operations in order to return. /// /// > Note: Currently only concrete distributed actors types can be resolved. /// /// The actor system's ``DistributedActorSystem/resolve(id:as:)`` method is called by the compiler whenever end-users /// call the ``DistributedActor``'s ``DistributedActor/resolve(id:using:)`` method. The return types of those methods differ, /// as the actor system's return type is `Act?` (and it may throw if unable to resolve the `ActorID`). /// /// The actor system's `resolve` returning `nil` means that the ``ActorID`` passed to it refers to a _remote_ /// distributed actor. The Swift runtime reacts to this by creating a remote actor reference (sometimes called a "proxy"). /// /// /// ### Handling remote calls /// /// Finally, calls on a _remote_ distributed actor reference's distributed methods are turned into invocations of /// `remoteCall(on:target:invocation:returning:throwing:)` (or `remoteCallVoid(on:target:invocation:throwing:)` for Void returning methods). /// /// Implementing the remote calls correctly and efficiently is the important task for a distributed actor system library. /// /// Implementations of remote calls generally will serialize `actor.id`, `target` and `invocation` /// into some form of wire envelope, and send it over the network (or process boundary) using some /// transport mechanism of their choice. As they do so, they need to suspend the `remoteCall` function, /// and resume it once a reply to the call arrives. Unless the transport layer is also async/await aware, /// this will often require making use of a ``CheckedContinuation``. /// /// While implementing remote calls please keep in mind any potential failure scenarios that may occur, /// such as message loss, connection failures and similar issues. Such situations should all be /// surfaced by resuming the `remoteCall` by throwing an error conforming to ``DistributedActorSystemError``. /// /// While it is not _required_ to conform error thrown out of these methods to ``DistributedActorSystemError``, /// the general guideline about conforming errors to this protocol is that errors which are outside of the user's control, /// but are thrown because transport or actor system issues, should conform to it. This is to simplify /// separating "business logic errors" from transport errors. /// /// /// ### Further reading /// /// For an even more in-depth explanation about the inner workings of a distributed actor system, /// you can refer to the following Swift Evolution proposals: /// /// - [SE-0336: Distributed Actor Isolation](https://github.com/apple/swift-evolution/blob/main/proposals/0336-distributed-actor-isolation.md) /// - [SE-0344: Distributed Actor Runtime](https://github.com/apple/swift-evolution/blob/main/proposals/0344-distributed-actor-runtime.md) @available(SwiftStdlib 5.7, *) public protocol DistributedActorSystem: Sendable { /// The type ID that will be assigned to any distributed actor managed by this actor system. /// /// ### A note on Codable IDs /// If this type is ``Codable``, then any `distributed actor` using this `ActorID` as its ``DistributedActor/ID`` /// will gain a synthesized ``Codable`` conformance which is implemented by encoding the `ID`. /// The decoding counter part of the ``Codable`` conformance is implemented by decoding the `ID` and passing it to /// the ``DistributedActor/resolve(id:using:)`` method. associatedtype ActorID: Sendable & Hashable /// Type of ``DistributedTargetInvocationEncoder`` that should be used when the Swift runtime needs to encode /// a distributed target call into an encoder, before passing it off to `remoteCall(...)`. associatedtype InvocationEncoder: DistributedTargetInvocationEncoder /// Type of ``DistributedTargetInvocationDecoder`` that should be used when decoding invocations during /// ``executeDistributedTarget(on:target:invocationDecoder:handler:)`` calls. associatedtype InvocationDecoder: DistributedTargetInvocationDecoder /// The type of the result handler which will be offered the results /// returned by a distributed function invocation called via /// ``executeDistributedTarget(on:target:invocationDecoder:handler:)``. associatedtype ResultHandler: DistributedTargetInvocationResultHandler /// The serialization requirement that will be applied to all distributed targets used with this system. associatedtype SerializationRequirement // TODO: constrain SerializationRequirement in type-system to only be ok with protocol or class here // ==== --------------------------------------------------------------------- // - MARK: Resolving actors by identity /// Resolves a local or remote ``ActorID`` to a reference to given actor, or throws if unable to. /// /// The returned value is either a local actor or proxy to a remote actor. /// /// This function is not intended to be used directly, but instead is called by the Swift runtime /// whenever ``DistributedActor/resolve(id:using:)` or a concrete distributed actor's `init(from:)` is invoked. /// /// This function should either return an existing actor reference, or `nil` to signal that a remote distributed actor /// "proxy" should be created for this ``ActorID``. If the resolve fails, meaning that it can neither locate a local /// actor managed by this actor system, nor identify that the identity is located on some remote actor system, then /// this function should throw. /// /// ```swift /// distributed actor Worker { /* ... */ } /// /// // the following internally calls actorSystem.resolve(id: id, as: Worker.self) /// let worker: Worker = try Worker.resolve(id: id, using: actorSystem) /// ``` /// /// If this function returns correctly, the returned actor reference is immediately /// usable. It may not necessarily imply the strict *existence* of a remote actor /// the identity was pointing towards, e.g. when a remote system allocates actors /// lazily as they are first time messaged to, however this should not be a concern /// of the sending side. /// /// Detecting liveness of such remote actors shall be offered / by transport libraries /// by other means, such as "watching an actor for termination" or similar. /// /// - Parameter id: The `ActorID` to resolve an actor reference for /// - Parameter actorType: The type of distributed actor the ID is expected to point at. /// /// - Throws: When unable to confirm if the `id` is correct, the resolved actor does not match the expected `actorType`, /// or any other internal validation error within the actor system's resolve process occurs. func resolve(id: ActorID, as actorType: Act.Type) throws -> Act? where Act: DistributedActor, Act.ID == ActorID // ==== --------------------------------------------------------------------- // - MARK: Actor Lifecycle /// Assign an ``ActorID`` for the passed actor type. /// /// This function is invoked by a distributed actor during its initialization, /// and the returned address value is stored along with it for the time of its /// lifetime. /// /// The address MUST uniquely identify the actor, and allow resolving it. /// E.g. if an actor is created under address `addr1` then immediately invoking /// `system.resolve(id: addr1, as: Greeter.self)` MUST return a reference /// to the same actor. func assignID(_ actorType: Act.Type) -> ActorID where Act: DistributedActor, Act.ID == ActorID /// Invoked during a distributed actor's initialization, as soon as it becomes fully initialized. /// /// The system is expected to store the reference to this actor, and maintain an `ActorID: DistributedActor` /// mapping for the purpose of implementing the `resolve(id:as:)` method. /// /// The system usually should NOT retain the passed reference, and it will be informed via /// ``resignID(_:)`` when the actor has been deallocated so it can remove the stale reference from its /// internal `ActorID: DistributedActor` mapping. /// /// The ``DistributedActor/id`` of the passed actor must be an ``ActorID`` that this system previously has assigned. /// /// If `actorReady` gets called with some unknown ID, it should crash immediately as it signifies some /// very unexpected use of the system. /// /// - Parameter actor: reference to the (local) actor that was just fully initialized. func actorReady(_ actor: Act) where Act: DistributedActor, Act.ID == ActorID /// Called during when a distributed actor is deinitialized, or fails to initialize completely (e.g. by throwing /// out of an `init` that did not completely initialize all of the actors stored properties yet). /// /// This method is guaranteed to be called at-most-once for a given id (assuming IDs are unique, /// and not re-cycled by the system), i.e. if it is called during a failure to initialize completely, /// the call from the actor's deinitializer will not happen (as under these circumstances, `deinit` will be run). /// /// If `resignID` gets called with some unknown ID, it should crash immediately as it signifies some /// very unexpected use of the system. /// /// - Parameter id: the id of an actor managed by this system that has begun its `deinit`. func resignID(_ id: ActorID) // ==== --------------------------------------------------------------------- // - MARK: Remote Method Invocations /// Invoked by the Swift runtime when a distributed remote call is about to be made. /// /// The returned `DistributedTargetInvocation` will be populated with all /// arguments, generic substitutions, and specific error and return types /// that are associated with this specific invocation. func makeInvocationEncoder() -> InvocationEncoder /// Invoked by the Swift runtime when making a remote call. /// /// The `arguments` are the arguments container that was previously created /// by `makeInvocationEncoder` and has been populated with all arguments. /// /// This method should perform the actual remote function call, and await for its response. /// /// ## Serialization Requirement /// Implementations of this method must ensure that the `Argument` type parameter conforms /// to the types' `SerializationRequirement`. /// /// ## Errors /// This method is allowed to throw because of underlying transport or serialization errors, /// as well as by re-throwing the error received from the remote callee (if able to). @available(SwiftStdlib 6.0, *) func remoteCall( on actor: Act, target: RemoteCallTarget, invocation: inout InvocationEncoder, throwing: Err.Type, returning: Res.Type ) async throws -> Res where Act: DistributedActor, Act.ID == ActorID, Err: Error // Res: SerializationRequirement /// Invoked by the Swift runtime when making a remote call. /// /// The `arguments` are the arguments container that was previously created /// by `makeInvocationEncoder` and has been populated with all arguments. /// /// This method should perform the actual remote function call, and await for its response. /// /// ## Errors /// This method is allowed to throw because of underlying transport or serialization errors, /// as well as by re-throwing the error received from the remote callee (if able to). @available(SwiftStdlib 6.0, *) func remoteCallVoid( on actor: Act, target: RemoteCallTarget, invocation: inout InvocationEncoder, throwing: Err.Type ) async throws where Act: DistributedActor, Act.ID == ActorID, Err: Error // Implementation notes: // The `metatype` must be the type of `Value`, and it must conform to // `SerializationRequirement`. If it does not, the method will crash at // runtime. This is because we cannot express // `Value: SerializationRequirement`, however the generic `Value` is still // useful since it allows us to avoid boxing the value into an existential, // before we'd right away unbox it as first thing in the implementation of // this function. /// Implementation synthesized by the compiler. /// Not intended to be invoked explicitly from user code! func invokeHandlerOnReturn( handler: ResultHandler, resultBuffer: UnsafeRawPointer, metatype: Any.Type ) async throws } // ==== ---------------------------------------------------------------------------------------------------------------- // MARK: Execute Distributed Methods @available(SwiftStdlib 5.7, *) extension DistributedActorSystem { /// Prepare and execute a call to the distributed function identified by the passed arguments, /// on the passed `actor`, and collect its results using the `ResultHandler`. /// /// This method encapsulates multiple steps that are invoked in executing a distributed function, /// into one very efficient implementation. The steps involved are: /// /// - looking up the distributed function based on its name /// - decoding, in an efficient manner, all arguments from the `Args` container into a well-typed representation /// - using that representation to perform the call on the target method /// /// The reason for this API using a `ResultHandler` rather than returning values directly, /// is that thanks to this approach it can avoid any existential boxing, and can serve the most /// latency sensitive-use-cases. /// /// - Parameters: /// - actor: actor on which the remote call should invoke the target /// - target: the target (method) identifier that should be invoked /// - invocationDecoder: used to obtain all arguments to be used to perform /// the target invocation /// - handler: used to provide a type-safe way for library code to handle /// the values returned by the target invocation. /// - Throws: if the target location, invocation argument decoding, or /// some other mismatch between them happens. In general, this /// method is allowed to throw in any situation that might otherwise /// result in an illegal or unexpected invocation being performed. /// /// Throws ``ExecuteDistributedTargetMissingAccessorError`` if the `target` /// does not resolve to a valid distributed function accessor, i.e. the /// call identifier is incorrect, corrupted, or simply not present in this process. public func executeDistributedTarget( on actor: Act, target: RemoteCallTarget, invocationDecoder: inout InvocationDecoder, handler: Self.ResultHandler ) async throws where Act: DistributedActor { // NOTE: Implementation could be made more efficient because we still risk // demangling a RemoteCallTarget identity (if it is a mangled name) multiple // times. We would prefer to store if it is a mangled name, demangle, and // always refer to that demangled repr perhaps? We do cache the resulting // pretty formatted name of the call target, but perhaps we can do better. // Get the expected parameter count of the func let targetName = target.identifier let targetNameUTF8 = Array(targetName.utf8) // Gen the generic environment (if any) associated with the target. let genericEnv = unsafe targetNameUTF8.withUnsafeBufferPointer { targetNameUTF8 in unsafe _getGenericEnvironmentOfDistributedTarget( targetNameUTF8.baseAddress!, UInt(targetNameUTF8.endIndex)) } var substitutionsBuffer: UnsafeMutablePointer? = nil var witnessTablesBuffer: UnsafeRawPointer? = nil var numWitnessTables: Int = 0 defer { unsafe substitutionsBuffer?.deallocate() unsafe witnessTablesBuffer?.deallocate() } if let genericEnv = unsafe genericEnv { let subs = try invocationDecoder.decodeGenericSubstitutions() if subs.isEmpty { throw ExecuteDistributedTargetError( message: "Cannot call generic method without generic argument substitutions", errorCode: .missingGenericSubstitutions) } unsafe substitutionsBuffer = .allocate(capacity: subs.count) for (offset, substitution) in subs.enumerated() { let element = unsafe substitutionsBuffer?.advanced(by: offset) unsafe element?.initialize(to: substitution) } unsafe (witnessTablesBuffer, numWitnessTables) = unsafe _getWitnessTablesFor(environment: genericEnv, genericArguments: substitutionsBuffer!) if numWitnessTables < 0 { throw ExecuteDistributedTargetError( message: "Generic substitutions \(subs) do not satisfy generic requirements of \(target) (\(targetName))", errorCode: .invalidGenericSubstitutions) } } let paramCount = unsafe targetNameUTF8.withUnsafeBufferPointer { targetNameUTF8 in unsafe __getParameterCount( targetNameUTF8.baseAddress!, UInt(targetNameUTF8.endIndex)) } guard paramCount >= 0 else { throw ExecuteDistributedTargetError( message: """ Failed to decode distributed invocation target expected parameter count, error code: \(paramCount) mangled name: \(targetName) """, errorCode: .invalidParameterCount) } // Prepare buffer for the parameter types to be decoded into: let argumentTypesBuffer = UnsafeMutableBufferPointer.allocate(capacity: Int(paramCount)) defer { unsafe argumentTypesBuffer.deallocate() } // Demangle and write all parameter types into the prepared buffer let decodedNum = unsafe targetNameUTF8.withUnsafeBufferPointer { targetNameUTF8 in unsafe __getParameterTypeInfo( targetNameUTF8.baseAddress!, UInt(targetNameUTF8.endIndex), genericEnv, substitutionsBuffer, argumentTypesBuffer.baseAddress!._rawValue, Int(paramCount)) } // Fail if the decoded parameter types count seems off and fishy guard decodedNum == paramCount else { throw ExecuteDistributedTargetError( message: """ Failed to decode the expected number of params of distributed invocation target, error code: \(decodedNum) decoded: \(decodedNum), expected params: \(paramCount) mangled name: \(targetName) """, errorCode: .invalidParameterCount) } // Copy the types from the buffer into a Swift Array var argumentTypes: [Any.Type] = [] do { argumentTypes.reserveCapacity(Int(decodedNum)) for unsafe argumentType in unsafe argumentTypesBuffer { argumentTypes.append(argumentType) } } // Decode the return type func doAllocateReturnTypeBuffer(_: R.Type) -> UnsafeMutableRawPointer? { return UnsafeMutableRawPointer(UnsafeMutablePointer.allocate(capacity: 1)) } let maybeReturnTypeFromTypeInfo = unsafe targetNameUTF8.withUnsafeBufferPointer { targetNameUTF8 in unsafe __getReturnTypeInfo( /*targetName:*/targetNameUTF8.baseAddress!, /*targetLength:*/UInt(targetNameUTF8.endIndex), /*genericEnv:*/genericEnv, /*genericArguments:*/substitutionsBuffer) } guard let returnTypeFromTypeInfo: Any.Type = maybeReturnTypeFromTypeInfo else { throw ExecuteDistributedTargetError( message: "Failed to decode distributed target return type", errorCode: .typeDeserializationFailure) } guard let resultBuffer = _openExistential(returnTypeFromTypeInfo, do: unsafe doAllocateReturnTypeBuffer) else { throw ExecuteDistributedTargetError( message: "Failed to allocate buffer for distributed target return type", errorCode: .typeDeserializationFailure) } // we start out assuming we may have thrown, // and set this to false when we return without having thrown var executeDistributedTargetHasThrown = true func doDestroyReturnTypeBuffer(_: R.Type) { let buf = unsafe resultBuffer.assumingMemoryBound(to: R.self) if !executeDistributedTargetHasThrown { // since the _execute function has NOT thrown, // there must be a value in the result buffer that we must deinitialize unsafe buf.deinitialize(count: 1) } // otherwise, the _execute has thrown and not populated the result buffer // finally, deallocate the buffer unsafe buf.deallocate() } defer { _openExistential(returnTypeFromTypeInfo, do: doDestroyReturnTypeBuffer) } do { let returnType = try invocationDecoder.decodeReturnType() ?? returnTypeFromTypeInfo // let errorType = try invocationDecoder.decodeErrorType() // TODO(distributed): decide how to use when typed throws are done // Execute the target! try unsafe await _executeDistributedTarget( on: actor, /*targetNameData:*/targetName, /*targetNameLength:*/UInt(targetName.count), argumentDecoder: &invocationDecoder, argumentTypes: argumentTypesBuffer.baseAddress!._rawValue, resultBuffer: resultBuffer._rawValue, substitutions: UnsafeRawPointer(substitutionsBuffer), witnessTables: witnessTablesBuffer, numWitnessTables: UInt(numWitnessTables) ) // execute has not thrown, so the result buffer has been filled with some value, // we must properly deinitialize it. executeDistributedTargetHasThrown = false if returnType == Void.self { try await handler.onReturnVoid() } else { try unsafe await self.invokeHandlerOnReturn( handler: handler, resultBuffer: resultBuffer, metatype: returnType ) } } catch { try await handler.onThrow(error: error) } } } /// Represents a 'target' of a distributed call, such as a `distributed func` or /// `distributed` computed property. Identification schemes may vary between /// systems, and are subject to evolution. /// /// Actor systems generally should treat the `identifier` as an opaque string, /// and pass it along to the remote system for in their `remoteCall` /// implementation. Alternative approaches are possible, where the identifiers /// are either compressed, cached, or represented in other ways, as long as the /// recipient system is able to determine which target was intended to be /// invoked. /// /// The string representation will attempt to pretty print the target identifier, /// however its exact format is not specified and may change in future versions. @available(SwiftStdlib 5.7, *) public struct RemoteCallTarget: CustomStringConvertible, Hashable { private let _identifier: String public init(_ identifier: String) { self._identifier = identifier } /// The underlying identifier of the target, returned as-is. public var identifier: String { return _identifier } /// Attempts to pretty format the underlying target identifier. /// If unable to, returns the raw underlying identifier. public var description: String { if let name = _getFunctionFullNameFromMangledName(mangledName: _identifier) { return name } else { return "\(_identifier)" } } } @available(SwiftStdlib 5.7, *) @_silgen_name("swift_distributed_execute_target") func _executeDistributedTarget( on actor: AnyObject, // : DistributedActor _ targetName: UnsafePointer, _ targetNameLength: UInt, argumentDecoder: inout D, argumentTypes: Builtin.RawPointer, resultBuffer: Builtin.RawPointer, substitutions: UnsafeRawPointer?, witnessTables: UnsafeRawPointer?, numWitnessTables: UInt ) async throws /// Used to encode an invocation of a distributed target (method or computed property). /// /// ## Forming an invocation /// /// On the sending-side an instance of an invocation is constructed by the runtime, /// and calls to: `recordGenericSubstitution`, `recordArgument`, `recordReturnType`, /// `recordErrorType`, and finally `doneRecording` are made (in this order). /// /// If the return type of the target is `Void` the `recordReturnType` is not invoked. /// /// If the error type thrown by the target is not defined the `recordErrorType` is not invoked. /// /// An invocation implementation may decide to perform serialization right-away in the /// `record...` invocations, or it may choose to delay doing so until the invocation is passed /// to the `remoteCall`. This decision largely depends on if serialization is allowed to happen /// on the caller's task, and if any smarter encoding can be used once all parameter calls have been /// recorded (e.g. it may be possible to run-length encode values of certain types etc.) /// /// Once encoded, the system should use some underlying transport mechanism to send the /// bytes serialized by the invocation to the remote peer. /// /// ## Decoding an invocation /// Since every actor system is going to deal with a concrete invocation type, they may /// implement decoding them whichever way is most optimal for the given system. /// /// Once decided, the invocation must be passed to `executeDistributedTarget` /// which will decode the substitutions, argument values, return and error types (in that order). /// /// Note that the decoding will be provided the specific types that the sending side used to preform the call, /// so decoding can rely on simply invoking e.g. `Codable` (if that is the `SerializationRequirement`) decoding /// entry points on the provided types. @available(SwiftStdlib 5.7, *) public protocol DistributedTargetInvocationEncoder { /// The serialization requirement that the types passed to `recordArgument` and `recordReturnType` are required to conform to. associatedtype SerializationRequirement /// The arguments must be encoded order-preserving, and once `decodeGenericSubstitutions` /// is called, the substitutions must be returned in the same order in which they were recorded. /// /// - Parameter type: a generic substitution type to be recorded for this invocation. mutating func recordGenericSubstitution(_ type: T.Type) throws /// Record an argument of `Argument` type. /// This will be invoked for every argument of the target, in declaration order. /// /// ### Serialization Requirement /// Implementations of this method must ensure that the `Value` type parameter conforms /// to the types' `SerializationRequirement`. @available(SwiftStdlib 6.0, *) mutating func recordArgument( _ argument: RemoteCallArgument ) throws /// Record the error type of the distributed method. /// This method will not be invoked if the target is not throwing. /// /// - Parameter type: the type of error that was declared to be thrown by the invocation target. Currently this can only ever be `Error.self`. mutating func recordErrorType(_ type: E.Type) throws /// Record the return type of the distributed method. /// This method will not be invoked if the target is returning `Void`. /// /// ### Serialization Requirement /// Implementations of this method must ensure that the `R` type parameter conforms /// to the types' `SerializationRequirement`. @available(SwiftStdlib 6.0, *) mutating func recordReturnType(_ type: R.Type) throws /// Invoked to signal to the encoder that no further `record...` calls will be made on it. /// /// Useful if the encoder needs to perform some "final" task before the underlying message is considered complete, /// e.g. computing a checksum, or some additional message signing or finalization step. mutating func doneRecording() throws } /// Represents an argument passed to a distributed call target. @available(SwiftStdlib 5.7, *) public struct RemoteCallArgument { /// The "argument label" of the argument. /// The label is the name visible name used in external calls made to this /// target, e.g. for `func hello(label name: String)` it is `label`. /// /// If no label is specified (i.e. `func hi(name: String)`), the `label`, /// value is empty, however `effectiveLabel` is equal to the `name`. /// /// In most situations, using `effectiveLabel` is more useful to identify /// the user-visible name of this argument. public let label: String? /// The effective label of this argument. This reflects the semantics /// of call sites of function declarations without explicit label /// definitions in Swift. /// /// For example, for a method declared like `func hi(a: String)` the effective /// label is `a` while for a method like `func hi(a b: String)` or /// `func hi(_ b: String)` the label is the explicitly declared one, /// so `a` and `_` respectively. public var effectiveLabel: String { if let label { return label } else { return "_" } } /// The internal name of parameter this argument is accessible as in the /// function body. It is not part of the functions API and may change without /// breaking the target identifier. /// /// If the method did not declare an explicit `label`, it is used as the /// `effectiveLabel`. public let name: String /// The value of the argument being passed to the call. /// As `RemoteCallArgument` is always used in conjunction with /// `recordArgument` and populated by the compiler, this Value will generally /// conform to a distributed actor system's `SerializationRequirement`. public let value: Value public init(label: String?, name: String, value: Value) { self.label = label self.name = name self.value = value } } /// Decoder that must be provided to `executeDistributedTarget` and is used /// by the Swift runtime to decode arguments of the invocation. /// /// ### Decoding DistributedActor arguments using Codable /// When using an actor system where ``ActorID`` is ``Codable``, every distributed actor using that system /// is also implicitly ``Codable`` (see ``DistributedActorSystem``). Such distributed actors are encoded /// as their ``ActorID`` stored in an ``Encoder/singleValueContainer``. When ``Codable`` is being used /// by such a system, the ``decodeNextArgument`` method will be using ``Decoder`` to /// decode the incoming values, which may themselves be distributed actors. /// /// An actor system must be provided to the ``Decoder`` in order for a distributed actor's ``Decodable/init(from:)`` /// to be able to return the instance of the actor. Specifically, the decoded``ActorID`` is passed to the actor system's `resolve(id:as:)` method in order to /// return either a local instance identified by this ID, or creating a remote actor reference. /// Thus, you must set the actor system the decoding is performed for, on the decoder's `userInfo`, as follows: /// /// ``` /// mutating func decodeNextArgument() throws -> Argument { /// let argumentData: Data = /// ... /// // ... /// decoder.userInfo[.actorSystemKey] = self.actorSystem /// return try Argument.decode( /// } /// ``` @available(SwiftStdlib 5.7, *) public protocol DistributedTargetInvocationDecoder { /// The serialization requirement that the types passed to `decodeNextArgument` are required to conform to. /// The type returned by `decodeReturnType` is also expected to conform to this associated type requirement. associatedtype SerializationRequirement /// Decode all generic substitutions that were recorded for this invocation. /// /// The values retrieved from here must be in the same order as they were recorded by /// ``DistributedTargetInvocationEncoder/recordGenericSubstitution(_:)``. /// /// - Returns: array of all generic substitutions necessary to execute this invocation target. /// - Throws: if decoding substitutions fails. mutating func decodeGenericSubstitutions() throws -> [Any.Type] /// Attempt to decode the next argument from the underlying buffers into pre-allocated storage /// pointed at by 'pointer'. /// /// This method should throw if it has no more arguments available, if decoding the argument failed, /// or, optionally, if the argument type we're trying to decode does not match the stored type. /// /// The result of the decoding operation must be stored into the provided 'pointer' rather than /// returning a value. This pattern allows the runtime to use a heavily optimized, pre-allocated /// buffer for all the arguments and their expected types. The 'pointer' passed here is a pointer /// to a "slot" in that pre-allocated buffer. That buffer will then be passed to a thunk that /// performs the actual distributed (local) instance method invocation. /// /// ### Serialization Requirement /// Implementations of this method must ensure that the `Argument` type parameter conforms /// to the types' `SerializationRequirement`. @available(SwiftStdlib 6.0, *) mutating func decodeNextArgument() throws -> Argument /// Decode the specific error type that the distributed invocation target has recorded. /// Currently this effectively can only ever be `Error.self`. /// /// If the target known to not be throwing, or no error type was recorded, the method should return `nil`. mutating func decodeErrorType() throws -> Any.Type? /// Attempt to decode the known return type of the distributed invocation. /// /// It is legal to implement this by returning `nil`, and then the system /// will take the concrete return type from the located function signature. mutating func decodeReturnType() throws -> Any.Type? } /// Protocol a distributed invocation execution's result handler. /// /// An instance conforming to this type must be passed when invoking /// ``executeDistributedTarget(on:target:invocationDecoder:handler:)`` while handling an incoming distributed call. /// /// The handler will then be invoked with the return value (or error) that the invoked target returned (or threw). @available(SwiftStdlib 5.7, *) public protocol DistributedTargetInvocationResultHandler { /// The serialization requirement that the value passed to `onReturn` is required to conform to. associatedtype SerializationRequirement /// Invoked when the distributed target execution returns successfully. /// The `value` is the return value of the executed distributed invocation target. /// /// ### Serialization Requirement /// Implementations of this method must ensure that the `Success` type parameter conforms /// to the types' `SerializationRequirement`. @available(SwiftStdlib 6.0, *) func onReturn(value: Success) async throws /// Invoked when the distributed target execution of a `Void` returning /// function has completed successfully. func onReturnVoid() async throws /// Invoked when the distributed target execution of a target has thrown an error. /// /// It is not guaranteed that the error conform to the ``SerializationRequirement``; /// This guarantee is only given to return values (and offered by `onReturn`). func onThrow(error: Err) async throws } /******************************************************************************/ /******************************** Errors **************************************/ /******************************************************************************/ /// Error protocol to which errors thrown by any `DistributedActorSystem` should conform. @available(SwiftStdlib 5.7, *) public protocol DistributedActorSystemError: Error {} /// Error thrown by ``DistributedActorSystem/executeDistributedTarget(on:target:invocationDecoder:handler:)``. /// /// Inspect the ``errorCode`` for details about the underlying reason this error was thrown. @available(SwiftStdlib 5.7, *) public struct ExecuteDistributedTargetError: DistributedActorSystemError { public let errorCode: ErrorCode public let message: String public enum ErrorCode { /// Unable to resolve the target identifier to a function accessor. /// This can happen when the identifier is corrupt, illegal, or wrong in the /// sense that the caller and callee do not have the called function recorded /// using the same identifier. case targetAccessorNotFound /// Call target has different number of parameters than arguments /// provided by the invocation decoder. case invalidParameterCount /// Target expects generic environment information, but invocation decoder /// provided no generic substitutions. case missingGenericSubstitutions /// Generic substitutions provided by invocation decoder are incompatible /// with target of the call. E.g. the generic requirements on the actual /// target could not be fulfilled by the obtained generic substitutions. case invalidGenericSubstitutions // Failed to deserialize type or obtain type information for call. case typeDeserializationFailure /// A general issue during the execution of the distributed call target occurred. case other } public init(message: String) { self.message = message self.errorCode = .other } public init(message: String, errorCode: ErrorCode) { self.message = message self.errorCode = errorCode } } /// Error thrown by distributed actor systems while encountering encoding/decoding /// issues. /// /// Also thrown when an attempt to decode ``DistributedActor`` is made, /// but no ``DistributedActorSystem`` is available in the `Decoder`'s /// `userInfo[.actorSystemKey]`, as it is required to perform the resolve call. @available(SwiftStdlib 5.7, *) public struct DistributedActorCodingError: DistributedActorSystemError { public let message: String public init(message: String) { self.message = message } public static func missingActorSystemUserInfo(_ actorType: Act.Type) -> Self where Act: DistributedActor { .init(message: "Missing DistributedActorSystem userInfo while decoding") } } /// Invoked when a distributed "stub" method would be incorrectly invoked on a /// local instance of the synthesized stub. Generally this should not be possible /// as a stub always should be used as "remote" and therefore no local calls /// are performed on them. Seeing this fatal error means a problem in the Swift /// Distributed runtime, please report an issue. @available(SwiftStdlib 6.0, *) public // COMPILER_INTRINSIC func _diagnoseDistributedStubMethodCalled( className: StaticString, funcName: StaticString = #function, file: StaticString = #file, line: UInt = #line, column: UInt = #column ) -> Never { #if !$Embedded fatalError("invoked distributed stub '\(className).\(funcName)'", file: file, line: line) #else Builtin.int_trap() #endif }