//===----------------------------------------------------------------------===// // // 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. /// Since those methods are not currently expressible as protocol requirements due to advanced use of generics /// combined with type aliases, they will not appear in the protocol's documentation as explicit requirements. /// Instead, we present their signatures that a conforming type has to implement here: /// /// > Note: Although the `remoteCall` methods are not expressed as protocol requirements in source, /// > the compiler will provide the same errors as-if they were declared explicitly in this protocol. /// /// ```swift /// /// 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). /// 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 /// ``` /// /// ```swift /// /// Invoked by the Swift runtime when making a `Void`-returning 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). /// func remoteCallVoid( /// on actor: Act, /// target: RemoteCallTarget, /// invocation: inout InvocationEncoder, /// throwing: Err.Type /// ) async throws -> Res /// where Act: DistributedActor, /// Act.ID == ActorID, /// Err: Error /// ``` /// /// 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 where SerializationRequirement == InvocationEncoder.SerializationRequirement, SerializationRequirement == InvocationDecoder.SerializationRequirement, SerializationRequirement == ResultHandler.SerializationRequirement // ==== --------------------------------------------------------------------- // - MARK: Resolving actors by identity /// Resolve a local or remote actor address to a real actor instance, or throw if unable to. /// The returned value is either a local actor or proxy to a remote actor. /// /// Resolving an actor is called when a specific distributed actors `init(from:)` /// decoding initializer is invoked. Once the actor's identity is deserialized /// using the `decodeIdentity(from:)` call, it is fed into this function, which /// is responsible for resolving the identity to a remote or local actor reference. /// /// If the resolve fails, meaning that it cannot locate a local actor managed for /// this identity, managed by this transport, nor can a remote actor reference /// be created for this identity on this transport, then this function must throw. /// /// 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. func resolve(id: ActorID, as actorType: Act.Type) throws -> Act? where Act: DistributedActor, Act.ID == ActorID // ==== --------------------------------------------------------------------- // - MARK: Actor Lifecycle /// Create 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 `actor.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. // /// // /// ## 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). // 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). // func remoteCallVoid( // on actor: Act, // target: RemoteCallTarget, // invocation: inout InvocationEncoder, // throwing: Err.Type // ) async throws -> Res // 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 nameUTF8 = Array(targetName.utf8) // Gen the generic environment (if any) associated with the target. let genericEnv = nameUTF8.withUnsafeBufferPointer { nameUTF8 in _getGenericEnvironmentOfDistributedTarget(nameUTF8.baseAddress!, UInt(nameUTF8.endIndex)) } var substitutionsBuffer: UnsafeMutablePointer? = nil var witnessTablesBuffer: UnsafeRawPointer? = nil var numWitnessTables: Int = 0 defer { substitutionsBuffer?.deallocate() witnessTablesBuffer?.deallocate() } if let genericEnv = genericEnv { let subs = try invocationDecoder.decodeGenericSubstitutions() if subs.isEmpty { throw ExecuteDistributedTargetError( message: "Cannot call generic method without generic argument substitutions", errorCode: .missingGenericSubstitutions) } substitutionsBuffer = .allocate(capacity: subs.count) for (offset, substitution) in subs.enumerated() { let element = substitutionsBuffer?.advanced(by: offset) element?.initialize(to: substitution) } (witnessTablesBuffer, numWitnessTables) = _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 = nameUTF8.withUnsafeBufferPointer { nameUTF8 in __getParameterCount(nameUTF8.baseAddress!, UInt(nameUTF8.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 { argumentTypesBuffer.deallocate() } // Demangle and write all parameter types into the prepared buffer let decodedNum = nameUTF8.withUnsafeBufferPointer { nameUTF8 in __getParameterTypeInfo( nameUTF8.baseAddress!, UInt(nameUTF8.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 argumentType in argumentTypesBuffer { argumentTypes.append(argumentType) } } // Decode the return type func allocateReturnTypeBuffer(_: R.Type) -> UnsafeRawPointer? { return UnsafeRawPointer(UnsafeMutablePointer.allocate(capacity: 1)) } guard let returnTypeFromTypeInfo: Any.Type = _getReturnTypeInfo(mangledMethodName: targetName, genericEnv: genericEnv, genericArguments: substitutionsBuffer) else { throw ExecuteDistributedTargetError( message: "Failed to decode distributed target return type", errorCode: .typeDeserializationFailure) } guard let resultBuffer = _openExistential(returnTypeFromTypeInfo, do: allocateReturnTypeBuffer) else { throw ExecuteDistributedTargetError( message: "Failed to allocate buffer for distributed target return type", errorCode: .typeDeserializationFailure) } func destroyReturnTypeBuffer(_: R.Type) { resultBuffer.assumingMemoryBound(to: R.self).deallocate() } defer { _openExistential(returnTypeFromTypeInfo, do: destroyReturnTypeBuffer) } do { let returnType = try invocationDecoder.decodeReturnType() ?? returnTypeFromTypeInfo // let errorType = try invocationDecoder.decodeErrorType() // TODO(distributed): decide how to use? // Execute the target! try await _executeDistributedTarget( on: actor, targetName, UInt(targetName.count), argumentDecoder: &invocationDecoder, argumentTypes: argumentTypesBuffer.baseAddress!._rawValue, resultBuffer: resultBuffer._rawValue, substitutions: UnsafeRawPointer(substitutionsBuffer), witnessTables: witnessTablesBuffer, numWitnessTables: UInt(numWitnessTables) ) if returnType == Void.self { try await handler.onReturnVoid() } else { try 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 { 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. mutating func recordGenericSubstitution(_ type: T.Type) throws // /// Ad-hoc requirement // /// // /// Record an argument of `Argument` type. // /// This will be invoked for every argument of the target, in declaration order. // mutating func recordArgument( // _ argument: DistributedTargetArgument // ) throws /// Record the error type of the distributed method. /// This method will not be invoked if the target is not throwing. mutating func recordErrorType(_ type: E.Type) throws // /// Ad-hoc requirement // /// // /// Record the return type of the distributed method. // /// This method will not be invoked if the target is returning `Void`. // mutating func recordReturnType(_ type: R.Type) throws 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, i.e. if no explicit `label` was set /// this defaults to the `name`. This reflects the semantics of call sites of /// function declarations without explicit label definitions in Swift. public var effectiveLabel: String { return label ?? name } /// 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. @available(SwiftStdlib 5.7, *) public protocol DistributedTargetInvocationDecoder { associatedtype SerializationRequirement mutating func decodeGenericSubstitutions() throws -> [Any.Type] // /// Ad-hoc protocol requirement // /// // /// 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. // mutating func decodeNextArgument() throws -> Argument 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? } @available(SwiftStdlib 5.7, *) public protocol DistributedTargetInvocationResultHandler { associatedtype SerializationRequirement // func onReturn(value: Success) async throws /// Invoked when the distributed target invocation of a `Void` returning /// function has completed successfully. func onReturnVoid() async throws 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") } }