profile
viewpoint
If you are wondering where the data of this site comes from, please visit https://api.github.com/users/ktoso/events. GitMemory does not store any data, but only uses NGINX to cache data for a period of time. The idea behind GitMemory is simply to give users a better reading experience.
Konrad `ktoso` Malawski ktoso We sell fruit. ¯\_(ツ)_/¯ I'm fine, thanks! Tokyo http://www.kto.so Concurrency & Distributed systems in Swift @ ; ex: Akka | Reactive Streams

apple/swift-evolution 12731

This maintains proposals for changes and user-visible enhancements to the Swift Programming Language.

git-commit-id/git-commit-id-maven-plugin 1272

Maven plugin which includes build-time git repository information into an POJO / *.properties). Make your apps tell you which version exactly they were built from! Priceless in large distributed deployments... :-)

apple/swift-cluster-membership 162

Distributed Membership Protocol implementations in Swift

apple/swift-service-discovery 161

A service discovery API for Swift.

apple/swift-distributed-tracing 62

Instrumentation library for Swift server applications

DougGregor/swift-evolution 35

This maintains proposals for changes and user-visible enhancements to the Swift Programming Language.

drexin/akka-io-file 30

An asynchronous file handling module for Akka IO

apple/swift-sample-distributed-actors-transport 19

Distributed actors transport example, for feature review

apple/swift-distributed-tracing-baggage 14

Minimal context propagation container with Logging

apple/swift-distributed-tracing-baggage-core 7

Minimal context propagation container

issue commentswift-server/swift-service-lifecycle

Fails to compile Xcode 13 RC (note: needs macOS release)

That seems to be the case AFAICS

joshuawright11

comment created time in 2 hours

PullRequestReviewEvent

Pull request review commentswift-server/guides

Swift Concurrency adoption guidelines for Swift Server Libraries

+# Swift Concurrency adoption guidelines for Swift Server Libraries++This writeup attempts to provide a set of guidelines to follow by authors of server-side Swift libraries. Specifically a lot of the discussion here revolves around what to do about existing APIs and libraries making extensive use of Swift NIO’s `EventLoopFuture` and related types.++Swift Concurrency is a multi-year effort. It is very valuable for the server community to participate in this multi-year adoption of the concurrency features, one by one, and provide feedback while doing so. As such, we should not hold off adopting concurrency features until Swift 6 as we may miss valuable opportunity to improve the concurrency model.++In 2021 we saw structured concurrency and actors arrive with Swift 5.5. Now is a great time to provide APIs using those primitives. In the future we will see fully checked Swift concurrency. This will come with breaking changes. For this reason adopting the new concurrency features can be split into two phases.+++## What you can do right now++### API Design++Firstly, existing libraries should strive to add `async` functions where possible to their user-facing “surface” APIs in addition to existing `*Future` based APIs wherever possible. These additive APIs can be gated on the Swift version and can be added without breaking existing users' code, for example like this:++```swift+extension Worker {+  func work() -> EventLoopFuture<Value> { ... }+  +  #if swift(>=5.5)+  func work() async throws -> Value { ... }+  #endif+}+```++If a function cannot fail but was using futures before, it should not include the `throws` keyword in its new incarnation. ++Such adoption can begin immediately, and should not cause any issues to existing users of existing libraries. ++### SwiftNIO helper functions++To allow an easy transition to async code, SwiftNIO offers a number of helper methods on `EventLoopFuture` and `-Promise`. Those live in the `_NIOConcurrency` module and will move to `NIOCore` once Swift concurrency is released.++On every `EventLoopFuture` you can call `.get()` to transition the future into an `await`-able invocation. If you want to translate async/await calls to an `EventLoopFuture` we recommend the following pattern:++```swift +#if swift(>=5.5)+func yourAsyncFunctionConvertedToAFuture(on eventLoop: EventLoop) +    -> EventLoopFuture<Result> {+    let promise = context.eventLoop.makePromise(of: Out.self)+    promise.completeWithTask {+        try await yourMethod(yourInputs)+    }+    return promise.futureResult+}+#endif+```++Further helpers exist for `EventLoopGroup`, `Channel`, `ChannelOutboundInvoker` and `ChannelPipeline`.++### Sendable Checking++> [SE-0302][SE-0302] introduced the `Sendable` protocol, which is used to indicate which types have values that can safely be copied across actors or, more generally, into any context where a copy of the value might be used concurrently with the original. Applied uniformly to all Swift code, `Sendable` checking eliminates a large class of data races caused by shared mutable state.+>+> -- from [Staging in Sendable checking][sendable-staging], which outlines the `Sendable` adoption plan for Swift 6.++In the future we will see fully checked Swift concurrency. The language features to support this are the `Sendable` protocol and the `@Sendable` keyword for closures. Since sendable checking will break existing Swift code, a new major Swift version is required.++To ease the transition to fully checked Swift code, it is possible to annotate your APIs with the `Sendable` protocol today.++You can start adopting Sendable and getting appropriate warnings in Swift 5.5 already by passing the `-warn-concurrency` flag, you can do so in SwiftPM for the entire project like so:++```+swift build -Xswiftc -Xfrontend -Xswiftc -warn-concurrency+```+++#### Sendable checking today++Sendable checking is currently disabled in Swift 5.5(.0) because it was causing a number of tricky situations for which we lacked the tools to resolve.++Most of these issues have been resolved on today’s `main` branch of the compiler, and are expected to land in the next Swift 5.5 releases. It may be worthwhile waiting for adoption until the next version(s) after 5.5.0.++For example, one of such capabilities is the ability for tuples of `Sendable` types to conform to `Sendable` as well. We recommend holding off adoption of `Sendable` until this patch lands in Swift 5.5 (which should be relatively soon). With this change, the difference between Swift 5.5 with `-warn-concurrency` enabled and Swift 6 mode should be very small, and manageable on a case by case basis.++#### Backwards compatibility of declarations and “checked” Swift Concurrency++Adopting Swift Concurrency will progressively cause more warnings, and eventually compile time errors in Swift 6 when sendability checks are violated, marking potentially unsafe code.++It may be difficult for a library to maintain a version that is compatible with versions prior to Swift 6 while also fully embracing the new concurrency checks. For example, it may be necessary to mark generic types as `Sendable`, like so:++```swift+struct Container<Value: Sendable>: Sendable { ... }+```++Here, the `Value` type must be marked `Sendable` for Swift 6’s concurrency checks to work properly with such container. However, since the `Sendable` type does not exist in Swift prior to Swift 5.5, it would be difficult to maintain a library that supports both Swift 5.4+ as well as Swift 6.++In such situations, it may be helpful to utilize the following trick to be able to share the same `Container` declaration between both Swift versions of the library:++```swift+#if compiler(>=5.5)+public typealias MYPREFIX_Sendable = Swift.Sendable+#else +public typealias MYPREFIX_Sendable = Any+#endif+```++The `Any` alias is effectively a no-op when applied as generic constraint, and thus this way it is possible to keep the same `Container<Value>` declaration working across Swift versions.++### Task Local Values and Logging++The newly introduced Task Local Values API ([SE-0311][SE-0311]) allows for implicit carrying of metadata along with `Task` execution. It is a natural fit for for tracing and carrying metadata around with task execution, and e.g. including it in log messages. ++We are working on adjusting [SwiftLog](https://github.com/apple/swift-log) to become powerful enough to automatically pick up and log specific task local values. This change will be introduced in a source compatible way. ++For now libraries should continue using logger metadata, but we expect that in the future a lot of the cases where metadata is manually passed to each log statement can be replaced with setting task local values. ++### Preparing for the concept of Deadlines++Deadlines are another feature that closely relate to Swift Concurrency, and were originally pitched during the early versions of the Structured Concurrency proposal and later on moved out of it. The Swift team remains interested in introducing deadline concepts to the language and some preparation for it already has been performed inside the concurrency runtime. Right now however, there is no support for deadlines in Swift Concurrency and it is fine to continue using mechanisms like `NIODeadline` or similar mechanisms to cancel tasks after some period of time has passed. ++Once Swift Concurrency gains deadline support, they will manifest in being able to cancel a task (and its child tasks) once such deadline (point in time) has been exceeded. For APIs to be “ready for deadlines” they don’t have to do anything special other than preparing to be able to deal with `Task`s and their cancellation.++### Cooperatively handling Task cancellation++`Task` cancellation exists today in Swift Concurrency and is something that libraries may already handle. In practice it means that any asynchronous function (or function which is expected to be called from within `Task`s), may use the [`Task.isCancelled`](https://developer.apple.com/documentation/swift/task/3814832-iscancelled) or [`try Task.checkCancellation()`](https://developer.apple.com/documentation/swift/task/3814826-checkcancellation) APIs to check if the task it is executing in was cancelled, and if so, it may cooperatively abort any operation it was currently performing.++Cancellation can be useful in long running operations, or before kicking off some expensive operation. For example, an HTTP client MAY check for cancellation before it sends a request — it perhaps does not make sense to send a request if it is known the task awaiting on it does not care for the result anymore after all!++Cancellation in general can be understood as “the one waiting for the result of this task is not interested in it anymore”, and it usually is best to throw a “cancelled” error when the cancellation is encountered. However, in some situations returning a “partial” result may also be appropriate (e.g. if a task is collecting many results, it may return those it managed to collect until now, rather than returning none or ignoring the cancellation and collecting all remaining results).++## What to expect with Swift 6++### Sendable: Global variables & imported code++Today, Swift 5.5 does not yet handle global variables at all within its concurrency checking model. This will soon change but the exact semantics are not set in stone yet. In general, avoid using global properties and variables wherever possible to avoid running into issues in the future. Consider deprecating global variables if able to.++Some global variables have special properties, such as `errno` which contains the error code of system calls. It is a thread local variable and therefore safe to read from any thread/`Task`. We expect to improve the importer to annotate such globals with some kind of “known to be safe” annotation, such that the Swift code using it, even in fully checked concurrency mode won’t complain about it. Having that said, using `errno` and other “thread local” APIs is very error prone in Swift Concurrency because thread-hops may occur at any suspension point, so the following snippet is very likely incorrect:++```swift+sys_call(...)+await ...+let err = errno // BAD, we are most likely on a different thread here (!)+```++Please take care when interacting with any thread-local API from Swift Concurrency. If your library had used thread local storage before, you will want to move them to use [task-local values](https://github.com/apple/swift-evolution/blob/main/proposals/0311-task-locals.md) instead as they work correctly with Swift’s structured concurrency tasks.++Another tricky situation is with imported C code. There may be no good way to annotate the imported types as Sendable (or it would be too troublesome to do so by hand). Swift is likely to gain improved support for imported code and potentially allow ignoring some of the concurrency safety checks on imported code. ++These relaxed semantics for imported code are not implemented yet, but keep it in mind when working with C APIs from Swift and trying to adopt the `-warn-concurrency` mode today. Please file any issues you hit on [bugs.swift.org](https://bugs.swift.org/secure/Dashboard.jspa) so we can inform the development of these checking heuristics based on real issues you hit.++### Custom Executors++We expect that Swift Concurrency will allow custom executors in the future. A custom executor would allow the ability to run actors / tasks “on” such executor. It is possible that `EventLoop`s could become such executors, however the custom executor proposal has not been pitched yet.++While we expect potential performance gains from using custom executors “on the same event loop” by avoiding asynchronous hops between calls to different actors, their introduction will not fundamentally change how NIO libraries are structured.++The guidance here will evolve as Swift Evolution proposals for Custom Executors are proposed, but don’t hold off adopting Swift Concurrency until custom executors “land” - it is important to start adoption early. For most code we believe that the gains from adopting Swift Concurrency vastly outweigh the slight performance cost actor-hops might induce.+++### Reduce use of Swift NIO Futures as “Concurrency Library“++Swift NIO currently provides a number of currency types for the Swift on Server ecosystem. Most notably `EventLoopFuture`s and `EventLoopPromise`s, that are used widely for asynchronous results. While the SSWG recommended using those at the API level in the past for easier interplay of server libraries, we advise to deprecate or remove such APIs once Swift 6 lands. The swift-server ecosystem should go all in on the structured concurrency features the languages provides. For this reason, it is crucial to provide async/await APIs today, to give your library users time to adopt the new APIs.++Some NIO types will remain however in the public interfaces of Swift on server libraries. We expect that networking clients and servers continue to be initialized with `EventLoopGroup`s. The underlying transport mechanism (`NIOPosix` and `NIOTransportServices`) should become implementation details however and should not be exposed to library adopters.++### NIO 3

Right, thanks Yim!

fabianfett

comment created time in 2 hours

PullRequestReviewEvent

push eventapple/swift-sample-distributed-actors-transport

Sven Weidauer

commit sha a1b9d5a09de548add3116c74978e42d56f83721d

Generate code based on analysis result

view details

Konrad `ktoso` Malawski

commit sha 327257530528018f3d1d25244b3a5393ce2c0840

Merge pull request #1 from 5sw/main Generate code based on analysis result

view details

push time in 20 hours

pull request commentapple/swift-sample-distributed-actors-transport

Generate code based on analysis result

Oh nice, thanks :)

I won't be nitpicking the implementation since it's all just a small example and string concats ad-hoc are good enough for the sample... Thanks a lot :-)

5sw

comment created time in 20 hours

pull request commentapple/swift-evolution

[Pitch] Distributed Actors

Thanks for the typo fixes 👍

ktoso

comment created time in 20 hours

Pull request review commentapple/swift-evolution

[Pitch] Distributed Actors

+# Distributed Actors++* Proposal: [SE-NNNN](NNNN-distributed-actors.md)+* Authors: [Konrad 'ktoso' Malawski](https://github.com/ktoso), [Dario Rexin](https://github.com/drexin), [Doug Gregor](https://github.com/DougGregor), [Tomer Doron](https://github.com/tomerd), [Kavon Farvardin](https://github.com/kavon)+* Review Manager: TBD+* Status: **Implementation in progress**+* Implementation: +  * Partially available in [recent `main` toolchain snapshots](https://swift.org/download/#snapshots) behind the `-enable-experimental-distributed` feature flag. +  * This flag also implicitly enables `-enable-experimental-concurrency`.+* Sample app:+  * A sample app, showcasing how the various "pieces" work together is available here:+    [https://github.com/apple/swift-sample-distributed-actors-transport](https://github.com/apple/swift-sample-distributed-actors-transport)++## Table of Contents+++<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->++<!-- code_chunk_output -->++- [Distributed Actors](#distributed-actors)+  - [Table of Contents](#table-of-contents)+  - [Introduction](#introduction)+  - [Motivation](#motivation)+  - [Proposed solution](#proposed-solution)+    - [Distributed actors](#distributed-actors-1)+      - [Properties](#properties)+      - [Methods](#methods)+      - [Initialization](#initialization)+    - [RPC Example](#rpc-example)+  - [Detailed design](#detailed-design)+    - [Location Transparency](#location-transparency)+    - [Typechecking Distributed Actors](#typechecking-distributed-actors)+      - [Protocol Conformances](#protocol-conformances)+        - [Default Conformances](#default-conformances)+      - [Distributed Methods](#distributed-methods)+    - [Distributed Actor lifecycle](#distributed-actor-lifecycle)+      - [Local Initialization](#local-initialization)+      - [Remote Resolution](#remote-resolution)+      - [Deinitialization](#deinitialization)+    - [Actor Transports](#actor-transports)+      - [Actor Identity](#actor-identity)+      - [Transporting Errors](#transporting-errors)+    - [Sharing and Discovery](#sharing-and-discovery)+      - [Distributed Actors are `Codable`](#distributed-actors-are-codable)+      - [Discovering Existing Instances](#discovering-existing-instances)+  - [Alternatives Considered](#alternatives-considered)+    - [Creating only a library and/or tool](#creating-only-a-library-andor-tool)+    - [Special Actor spawning APIs](#special-actor-spawning-apis)+      - [Explicit `spawn(transport)` keyword-based API](#explicit-spawntransport-keyword-based-api)+      - [Global eagerly initialized transport](#global-eagerly-initialized-transport)+      - [Directly adopt Akka-style Actors References `ActorRef<Message>`](#directly-adopt-akka-style-actors-references-actorrefmessage)+  - [Acknowledgments & Prior Art](#acknowledgments-prior-art)+  - [Source compatibility](#source-compatibility)+  - [Effect on ABI stability](#effect-on-abi-stability)+  - [Effect on API resilience](#effect-on-api-resilience)+  - [Changelog](#changelog)+- [Appendix](#appendix)+  - [Runtime implementation details](#runtime-implementation-details)+      - [Remote `distributed actor` instance allocation](#remote-distributed-actor-instance-allocation)+      - [`distributed func` internals](#distributed-func-internals)+  - [Future Directions](#future-directions)+      - [The `AnyActor` marker protocol](#the-anyactor-marker-protocol)+    - [Storing and requiring more specific Transport types](#storing-and-requiring-more-specific-transport-types)+    - [Ability to customize parameter/return type requirements](#ability-to-customize-parameterreturn-type-requirements)+    - [Resolving DistributedActor bound protocols](#resolving-distributedactor-bound-protocols)+    - [Synthesis of `_remote` and well-defined `Envelope<Message>` functions](#synthesis-of-_remote-and-well-defined-envelopemessage-functions)+    - [Support for `AsyncSequence`](#support-for-asyncsequence)+    - [Ability to hardcode actors to specific shared transport](#ability-to-hardcode-actors-to-specific-shared-transport)+    - [Actor Supervision](#actor-supervision)+  - [Related Work](#related-work)+    - [Swift Distributed Tracing integration](#swift-distributed-tracing-integration)+    - [Distributed `Deadline` propagation](#distributed-deadline-propagation)+    - [Potential Transport Candidates](#potential-transport-candidates)+  - [Background](#background)++<!-- /code_chunk_output -->++++## Introduction++Swift's [actors](https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md) are a relatively new building block for creating concurrent programs. The mutable state isolated by actors can only be interacted with by one task at any given time, eliminating a whole class of data races. But, the general [actor model](https://en.wikipedia.org/wiki/Actor_model) works equally well for distributed systems too.++This proposal introduces *distributed actors*, which are an extension of regular Swift actors that allows developers to take full advantage of the general actor model of computation. Distributed actors allow developers to scale their actor-based programs beyond a single process or node, without having to learn many new concepts.++Unlike regular actors, distributed actors take advantage of *[location transparency](https://en.wikipedia.org/wiki/Location_transparency)*, which relies on a robust, sharable handle (or name) for each actor instance. These handles can reference actor instances located in a different program process, which may even be running on a different machine that is accessible over a network. After resolving an actor handle to a specific instance located _somewhere_, the basic interactions with the actor instance closely mirrors that of regular actors. Distributed actors abstract over the communication mechanism that enables location transparency, so that the implementation can be extended to different domains. ++## Motivation++Distributed systems are not just for high-performance computing. Many of the programs written today are part of a distributed system. If your program uses *any* kind of [inter-process communication](https://en.wikipedia.org/wiki/Inter-process_communication) (IPC), i.e., it communicates with entities outside of its process on the same or a different machine, then it can be viewed as a distributed system. Real-world examples include networked applications, such as games, chat or media applications.

its is correct; it's the possessive form not the "it is" short form.

ktoso

comment created time in 20 hours

PullRequestReviewEvent

push eventktoso/swift-evolution

Konrad `ktoso` Malawski

commit sha 3e05273a78326bf18e9cb2e4038709885467236d

Apply suggestions from code review Thank you for the cleanups Co-authored-by: Cristian Kocza <kocza.cristian@gmail.com> Co-authored-by: Andreas Jönsson <5471361+nordicio@users.noreply.github.com>

view details

push time in 20 hours

Pull request review commentapple/swift-evolution

[Pitch] Distributed Actors

+# Distributed Actors++* Proposal: [SE-NNNN](NNNN-distributed-actors.md)+* Authors: [Konrad 'ktoso' Malawski](https://github.com/ktoso), [Dario Rexin](https://github.com/drexin), [Doug Gregor](https://github.com/DougGregor), [Tomer Doron](https://github.com/tomerd), [Kavon Farvardin](https://github.com/kavon)+* Review Manager: TBD+* Status: **Implementation in progress**+* Implementation: +  * Partially available in [recent `main` toolchain snapshots](https://swift.org/download/#snapshots) behind the `-enable-experimental-distributed` feature flag. +  * This flag also implicitly enables `-enable-experimental-concurrency`.+* Sample app:+  * A sample app, showcasing how the various "pieces" work together is available here:+    [https://github.com/apple/swift-sample-distributed-actors-transport](https://github.com/apple/swift-sample-distributed-actors-transport)++## Table of Contents+++<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->++<!-- code_chunk_output -->++- [Distributed Actors](#distributed-actors)+  - [Table of Contents](#table-of-contents)+  - [Introduction](#introduction)+  - [Motivation](#motivation)+  - [Proposed solution](#proposed-solution)+    - [Distributed actors](#distributed-actors-1)+      - [Properties](#properties)+      - [Methods](#methods)+      - [Initialization](#initialization)+    - [RPC Example](#rpc-example)+  - [Detailed design](#detailed-design)+    - [Location Transparency](#location-transparency)+    - [Typechecking Distributed Actors](#typechecking-distributed-actors)+      - [Protocol Conformances](#protocol-conformances)+        - [Default Conformances](#default-conformances)+      - [Distributed Methods](#distributed-methods)+    - [Distributed Actor lifecycle](#distributed-actor-lifecycle)+      - [Local Initialization](#local-initialization)+      - [Remote Resolution](#remote-resolution)+      - [Deinitialization](#deinitialization)+    - [Actor Transports](#actor-transports)+      - [Actor Identity](#actor-identity)+      - [Transporting Errors](#transporting-errors)+    - [Sharing and Discovery](#sharing-and-discovery)+      - [Distributed Actors are `Codable`](#distributed-actors-are-codable)+      - [Discovering Existing Instances](#discovering-existing-instances)+  - [Alternatives Considered](#alternatives-considered)+    - [Creating only a library and/or tool](#creating-only-a-library-andor-tool)+    - [Special Actor spawning APIs](#special-actor-spawning-apis)+      - [Explicit `spawn(transport)` keyword-based API](#explicit-spawntransport-keyword-based-api)+      - [Global eagerly initialized transport](#global-eagerly-initialized-transport)+      - [Directly adopt Akka-style Actors References `ActorRef<Message>`](#directly-adopt-akka-style-actors-references-actorrefmessage)+  - [Acknowledgments & Prior Art](#acknowledgments-prior-art)+  - [Source compatibility](#source-compatibility)+  - [Effect on ABI stability](#effect-on-abi-stability)+  - [Effect on API resilience](#effect-on-api-resilience)+  - [Changelog](#changelog)+- [Appendix](#appendix)+  - [Runtime implementation details](#runtime-implementation-details)+      - [Remote `distributed actor` instance allocation](#remote-distributed-actor-instance-allocation)+      - [`distributed func` internals](#distributed-func-internals)+  - [Future Directions](#future-directions)+      - [The `AnyActor` marker protocol](#the-anyactor-marker-protocol)+    - [Storing and requiring more specific Transport types](#storing-and-requiring-more-specific-transport-types)+    - [Ability to customize parameter/return type requirements](#ability-to-customize-parameterreturn-type-requirements)+    - [Resolving DistributedActor bound protocols](#resolving-distributedactor-bound-protocols)+    - [Synthesis of `_remote` and well-defined `Envelope<Message>` functions](#synthesis-of-_remote-and-well-defined-envelopemessage-functions)+    - [Support for `AsyncSequence`](#support-for-asyncsequence)+    - [Ability to hardcode actors to specific shared transport](#ability-to-hardcode-actors-to-specific-shared-transport)+    - [Actor Supervision](#actor-supervision)+  - [Related Work](#related-work)+    - [Swift Distributed Tracing integration](#swift-distributed-tracing-integration)+    - [Distributed `Deadline` propagation](#distributed-deadline-propagation)+    - [Potential Transport Candidates](#potential-transport-candidates)+  - [Background](#background)++<!-- /code_chunk_output -->++++## Introduction++Swift's [actors](https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md) are a relatively new building block for creating concurrent programs. The mutable state isolated by actors can only be interacted with by one task at any given time, eliminating a whole class of data races. But, the general [actor model](https://en.wikipedia.org/wiki/Actor_model) works equally well for distributed systems too.++This proposal introduces *distributed actors*, which are an extension of regular Swift actors that allows developers to take full advantage of the general actor model of computation. Distributed actors allow developers to scale their actor-based programs beyond a single process or node, without having to learn many new concepts.++Unlike regular actors, distributed actors take advantage of *[location transparency](https://en.wikipedia.org/wiki/Location_transparency)*, which relies on a robust, sharable handle (or name) for each actor instance. These handles can reference actor instances located in a different program process, which may even be running on a different machine that is accessible over a network. After resolving an actor handle to a specific instance located _somewhere_, the basic interactions with the actor instance closely mirrors that of regular actors. Distributed actors abstract over the communication mechanism that enables location transparency, so that the implementation can be extended to different domains. ++## Motivation++Distributed systems are not just for high-performance computing. Many of the programs written today are part of a distributed system. If your program uses *any* kind of [inter-process communication](https://en.wikipedia.org/wiki/Inter-process_communication) (IPC), i.e., it communicates with entities outside of its process on the same or a different machine, then it can be viewed as a distributed system. Real-world examples include networked applications, such as games, chat or media applications.++Distributed systems are pervasive, yet they require significant effort to implement in Swift. Consider this simple example of code that establishes a connection with a server and listens for messages that trigger some action:++```swift+import Foundation++actor Counter {+  var count: Int = 0+  func increment() {+    count += 1+  }+  func get() -> Int { return count }+}++class ConnectionManager {+  var connection: URLSessionWebSocketTask+  var state: Counter++  // Initialize a connection with the other server+  // and begin listening for messages.+  init?(_ serverURL: URLRequest, sharing counter: Counter) {+    state = counter+    connection = URLSession.shared.webSocketTask(with: serverURL)+    guard connection.error != nil else {+      return nil+    }+    listenLoop()+  }++  private func listenLoop() {+    Task.detached {+      switch try? await self.connection.receive() {+        case .some(let message):+          // NOTE: skip message deserialization; assume 1 message kind!+          await self.state.increment()  // perform action on local instance+          self.listenLoop() // listen for the next connection.+        default:+          return // end listen loop+      }+    }+  }++  deinit {+    connection.cancel(with: .normalClosure, reason: nil)+  }+}++let counter = Counter() // some shared actor instance++// Start the connection!+let serverReq = URLRequest(url: URL(string: "http://example.com:1337")!)+let c = ConnectionManager(serverReq, sharing: counter)+```++This is a barebones implementation of a unidirectional remote-procedure call (RPC), where the code above corresponds to the receiver's side that implements the requests, but does not perform responses. In this example, notice the reliance on a regular actor to synchronize accesses to its state, because the state can be accessed on the reciever's side too. While the sender-side code is omitted, it is already apparent that the basic pieces of an RPC-based distributed program involves:++1. Resolving / maintaining a connection.+2. Serializing and deserializing messages.+3. Servicing requests.++ The distributed actors in this proposal are designed to model an actor whose instance may be located in another program process. This means that the sender side can interact with the `Counter`'s state without the extra boilerplate. Specifically, distributed actors abstract over the actor's identifier and the communication transport, such as TCP/UDP, WebSocket or cross-process transports like XPC. This abstraction allows distributed code to focus on the implementation of the distributed actor's operations, using the familiar syntax and semantics of Swift's regular actors, and without needing to implement the communication in an ad-hoc manner. The same definition of a distributed actor is compatible with various identity and transport implementations, instances of which can even co-exist simultaneously in the same process.++## Proposed solution++There are several new concepts introduced by this proposal, which will be described in this section.++### Distributed actors++Distributed actors are a special flavor of the actor type that enforces additional rules on the type and its values, in order to achieve location transparency. They are written by prepending `distributed` to an actor declaration, like so:++```swift+distributed actor Player {+  let name: String+  var score: Int+  var teammates: [Player]+}+```++Distributed actors adopt the same isolation rules of regular actors, but because any instance may actually refer to an actor in another process (i.e., location transparency), extra rules are applied.++#### Properties+Developers should think carefully about operations that cross into the actor's isolation domain, because the cost of each operation can be expensive (e.g., if the actor is on a machine across the internet). Properties make it very easy to accidentially make multiple round-trips:++```swift+func example1(p: Player) async throws -> (String, Int) {+  try await (p.name, p.score) // ❌ might make two slow round-trips to `p`+}+```++Instead, methods are strongly encourged so that accesses can be batched.+Thus, access to a distributed actor's properties (stored and computed) from outside of the actor's isolation are forbidden. In addition, properties cannot be `nonisolated` or participate in a key-path.++#### Methods++Regular methods isolated to the distributed actor are not accessible from outside of the actor's isolation context. A new kind of method declaration, called a *distributed method*, are the only kind of isolated members that can be accessed from outside of a distributed actor. Nonisolated methods can be defined as usual, but a distributed method cannot be marked `nonisolated`. A distributed method is defined within a distributed actor by writing `distributed` in front of the method's declaration:++```swift+extension Player {+  distributed func addTeammate(p: Player) {+    guard !haveTeammate(p) else {+      return+    }+    teammates.append(p)+  }++  func haveTeammate(p: Player) -> Bool {+    return teammates.contains(p)+  }+}++func example2(a: Player, b: Player) async throws {+  a.haveTeammate(b) // ❌ error, function not `distributed`+  +  try await a.addTeammate(b)  // ✅ OK. distributed actors are Codable!+}+```++In addition to conforming to `Sendable`, a distributed function's parameters and its return type are all required to conform to the [`Codable` protocol](https://developer.apple.com/documentation/swift/codable). A codable value supports serialization and deserialization, which is nessecary in order to maintain location transparency. For example, servicing a method call on a distributed actor instance can involve sending the arguments to another process and awaiting a response.++Like a regular actor method, an expression that calls a distributed function is  treated as `async` from outside of the actor's isoalation. But for a distributed actor, such calls are _also_ treated as `throws` when outside of the actor's isolation, because a request to call a distributed method is not guaranteed to recieve a response. The underlying process that hosts the distributed actor instance may be on another machine that crashed, or a connection may be lost, etc. To help make this clear, consider the following example, which contains only the necessary `try` and `await` expressions:++```swift+distributed actor Greeter {+  distributed func englishGreeting() -> String { "Hello!" }+  distributed func japaneseGreeting() throws -> String { "こんにちは!" }+  distributed func germanGreeting() async -> String { "Servus!" }+  distributed func polishGreeting() async throws -> String { "Cześć!" }+  +  func inside() async throws { +    _ = self.englishGreeting()+    _ = try self.japaneseGreeting()+    _ = await self.germanGreeting()+    _ = try await self.polishGreeting()+  }+} // end of Greeter++func outside(greeter: Greeter) async throws { +  _ = try await greeter.englishGreeting()+  _ = try await greeter.japaneseGreeting()+  _ = try await greeter.germanGreeting()+  _ = try await greeter.polishGreeting()+}+```++Errors thrown by the underlying transport due to connection or messaging problems must conform to the `ActorTransportError` protocol. A distributed function can also be explicitly marked as `throws`, as shown above, but the underlying transport is responsible for determining how to forward any thrown errors, since errors thrown by a distributed function do _not_ have to be `Codable`.++One benefit of explicitly marking distributed functions with the `distributed` keyword that it makes clear to the programmer (and tools, such as IDEs) where networking costs are to be expected when using a distributed actor. This is an improvement over today's world, where any function might ultimately perform network calls, and we are not able to easily notice them, for example, in the middle of a tight loop.++#### Initialization+Distributed actors and functions are largely a set of a rules that enforce location transparency. In order for distributed actors to actually perform any remote communication, it is necessary to initialize them with an actual implementation of an `ActorTransport`, which is a protocol that is implementable by library writers. A *transport* handles all of the connection details that underpin distributed actors. An actor transport can take advantage of any networking, cross-process, or even in-memory approach to communicate between instances, as long as it conforms to the protocol correctly.++There are two ways to initialize a distributed actor, but in any case, a distributed actor is always associated with some `ActorTransport`. To create a new **local** instance of a distributed actor, call the actor's `init` as usual. All non-delegating initializers, which fully-initialize the distributed actor, are required to accept exactly one argument that conforms to `ActorTransport`:++```swift+distributed actor Capybara { +  var name: String+  +  // ✅ ok. exactly one transport param for non-delegating initializer.+  init(named name: String, using: ActorTransport) {+    self.name = name+  }++  // ✅ ok. no transport param, but it's a delegating initializer.+  convenience init() {+    self.init(named: "Happybara", using: Defaults.IPoAC)+  }++  // ❌ error, too many transport params for a non-delegating init.+  init(main: ActorTransport, alt: ActorTransport) {+    self.name = "Sleepybara"+  }+  +  // ❌ error, no transports for a non-delegating init.+  init(withName name: String) {+    self.name = name+  }+}+```++Swift will automatically associate a `Capybara` instance with the transport passed to `init(named:using:)`, without the programmer explicitly using the transport argument. This is done, instead of requiring a specific `let`-bound property to be initialized, because the `ActorTransport` is specially stored in the instance, depending on whether it represents a remote or local instance of the actor.++A **remote** instance can be only be resolved if the programmer also has the instance's `ActorIdentity`. An `ActorIdentity` is a protocol that specifies the minimum capabilities of a durable, unique identifier associated with a distributed actor instance. All distributed actors have two computed properties that are accessible from anywhere, and a static `resolve` function:++```swift+public protocol DistributedActor: Sendable, Codable, Identifiable, Hashable {++  /// 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.+  ///+  /// Conformance to this requirement is synthesized automatically for any+  /// `distributed actor` declaration.+  /*special*/ var id: AnyActorIdentity { get }++  /// Transport responsible for managing the lifecycle and messaging aspects of this actor.+  /*special*/ var transport: ActorTransport { get }+  +  static func resolve<Identity>(id identity: Identity, using transport: ActorTransport) +    throws -> Self?+    where Identity: ActorIdentity++}+```++We refer to the creation of a remote instance as "resolving" a remote actor, because it does *not* create an instance of the distributed actor if one does not exist. The actor's identity must be valid when calling `resolve` or resolution will fail:++```swift+extension Capybara {+  distributed func meet(friend: ActorIdentity) { ... }+}++func example3(transport: ActorTransport, friend name: ActorIdentity) async throws {+  // make local instance+  let a = Capybara(using: transport)++  // resolve some other instance+  let b = try Capybara.resolve(name, using: transport)+  +  // identities are Codable+  try await a.meet(b.id)++  // resolve a local instance from an ID+  let aliasForA = try Capybara.resolve(a.id, using: transport)+}+```++The examples so far have assumed that the actor's identity has already been obtained from *somewhere*. But, for a truly remote instance located in a different process, how does the current process obtain that actor's identity, in order to resolve it? ++This task of obtaining or exchanging actor identities is left to specific transport libraries, because it falls under the general problem of service discovery, whose solutions differ based on usage environments. In server environments, it is typical to have a single, fixed location where identity lookups may be performed. For example, those locations can be key-value databases, DNS lookups, or any other more specialized technique. In Swift's existing server ecosystem, the [swift-service-discovery](https://github.com/apple/swift-service-discovery) library can be used to exchange actor identities with a number of service discovery implementations. In the future, it is expected that additional actor-specialized libraries will become available.++### Distributed Actor Example++Now that we know distributed actor basics, we can rewrite the RPC example from earlier using distributed actors. We do so by changing `actor Counter`  to `distributed actor Counter`, and marking the `increment` and `get` methods as `distributed` too. This is all we need to do to prepare this actor for distributed calls.++```swift+import _Distributed++distributed actor Counter { +  var count: Int = 0+  +  distributed func increment() {+    count += 1+  }+  +  distributed func get() -> Int { +    return count+  }+}+```++For the RPC example, some process must first locally create an instance of the distributed actor `Counter`. This is done by calling`Counter.init(transport:)` and passing the specific transport we want it to accessed through:++```swift+import FishyTransport ++let transport = FishyTransport(...)+let counter = Counter(transport: transport)++/* Optional: register counter.id in some service discovery mechanism */+// discovery.announce("Counters", counter.id)+```+++Once we have either obtained the specific identity on a remote node, we can resolve it to get a reference to the remote actor, like this:++```swift+let id: AnyActorIdentity = try FishyTransport(host: "example.com", port: 1337, logLevel: nil)+  // Optional: instead of a fixed identity, get dynamic identity via service discovery, e.g.:+  //      await discovery.getAny("Counter", AnyActorIdentity.self)++let remoteCounter = try Counter.resolve(id, using: transport)+try await remoteCounter.increment() // remote call+```++#### Sample Transport / Application++In later sections, the details of implementing a transport are discussed. But, a core idea of distributed actors is to provide a clean separation between what the distributed object's API boundaries are, and how the inter-process communication is achieved. Thus, this proposal comes with a concrete sample application, along with a naive transport implementation, to demonstrate this separation: https://github.com/apple/swift-sample-distributed-actors-transport++The sample application forms a group of "nodes", each represented by an `ActorTransport` instance, and creates a number of `Chatter` distributed actors, all of which join a `ChatRoom` on a remote node. The chatters all communicate with the chat room and between eachother without having to know the transport details.++> **CAVEAT:** This sample project is very rough around the edges and is intended *only* to illustrate the ability to make use of the concepts introduced in this proposal. Please do not focus on the actual implementation details of the transport itself, but rather the interaction between it and the language proposal.++## Detailed design++For clarity, a number of details about this proposal were omitted from the Proposed Solution section. But, this section includes those details. Unless otherwise specified in this proposal, the semantics of a distributed actor are the same as a regular actor, as described in [SE-306](https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md).++### Location Transparency++The design of distributed actors intentionally does not provide facilities to easily determine whether an instance is local or remote. The programmer should not _need_ to think about where the instance is located, because Swift will make it work in either case. There are numerous benefits to embracing location transparency:++- The programmer can write a complex distributed systems algorithm and test it locally. Running that program on a cluster becomes merely a configuration and deployment change, without any additional source code changes.+- Distributed actors can be used with multiple transports without changing the actor's implementation.+- Actor instances can be balanced between nodes once capacity of a cluster changes, or be passivated when not in use, etc. There are many more advanced patterns for allocating instances, such as the "virtual actor" style as popularized by Orleans or Akka's cluster sharding.++One of the key restrictions that enable location transparency is the requirement that we pass arguments into the distributed actor that conform to `Codable`, so that they *can* be sent to another process if needed. But, there are some situations where the programmer _knows_ a particular instance is local to the process, so this restriction becomes bothersome.++#### Testing and breaking through Location Transparency++Programs based on distributed actors should always be written to respect location transparency, but sometimes it is useful to break through that abstraction. The most common situation where breaking through location transparency can be useful is when writing unit tests. Such tests may need to inspect state, or call non-distributed methods, of a distributed actor instance that is known to be local.++To support this kind of niche circumstance, all distributed actors offer a `withLocalDistributedActor` method, which executes a provided closure based on whether it is a local instance:++```swift+/// Runs the 'body' closure if and only if the passed 'actor' is a local instance.+/// +/// Returns `nil` if the actor was remote.+@discardableResult+func withLocalDistributedActor<Act, T>(+  _ actor: Act,+  _ body: (isolated Act) async throws -> T+) async rethrows -> T? where Act: DistributedActor+  +/// Runs the 'body' closure if and only if the passed 'actor' is a local instance.+/// +/// Invokes the 'else' closure if the actor instance was remote.+func withLocalDistributedActor<Act, T>(+  _ actor: Act,+  _ body: (isolated Act) async throws -> T,+  else whenRemote (Act) async throws -> T+) async rethrows -> T where Act: DistributedActor+```++When the instance is local, the `withLocalDistributedActor` method exposes the distributed actor instance to the provided closure, as if it were a regular actor instance. This means you can invoke non-distributed methods when the actor instance is local, without relying on hacks that would trigger a crash if invoked on a remote instance. ++Except for the `withLocalDistributedActor` method, it is not possible to pass a closure to a distributed actor, since closures are not `Codable`. But, relying on `withLocalDistributedActor` outside of niche circumstances like testing is considered bad practice.+++### Typechecking Distributed Actors++This section discusses the semantic checking and restrictions placed on distributed actor types.++#### Protocol Conformances++A distributed actor is the only type that can conform to the `DistributedActor` protocol, and all distributed actors conform to that protocol. This protocol's list of requirements are defined earlier in this proposal, under the [Initialization section](#initialization). ++Only `distributed` or `nonisolated` methods can be specified in a distributed actor protocol `P`, which is any protocol inheriting from `DistributedActor`. This follows under the same reasoning as why `private` members cannot be stated as requirements in a protocol: the member is not be accessible from outside of the type's implementation, so it is not part of the type's interface.++> **NOTE:** One exception to this analogy with `private` is the escape-hatch method `withLocalDistributedActor`, which can be used from outside of the distributed actor to strip away the location transparency. Then, an isolated method required by the protocol becomes exposed. But, this situation appears to be too niche to be worth supporting.++Importantly, a distributed actor _cannot_ conform to the Actor protocol. For example, such a conformance would fail to provide correct location transparency:++```swift+extension Actor {+  func f() -> NonCodableButSendableType { ... }+}++func g(mda: MyDistributedActor) async {+  let a: Actor = mda as Actor // ❌ error: must be disallowed because...+  let result = await a.f()    // we cannot recieve the result of `f` if remote!+}+```++Conversely, regular actors also cannot conform to the `DistributedActor` protocol because of the same principle. See the Alternatives Considered section for an `AnyActor` protocol that was under consideration, but excluded from this proposal.++> A simple future addition to this proposal would be an `AnyActor` marker protocol, to tie the Actor and DistributedActor into a common type hierarchy. We discuss this idea in [Future Directions](#future-directions).++Distributed actors *may* conform to protocols if it is possible conform to such protocol using distributed functions. For example, a protocol with only async and throwing requirements, is possible to be conformed to by a distributed actor, however only if all of the functon parameters and return types also respect the additional checks a distributed actor has:++```swift+protocol GreeterProtocol { +  func greet(name: String) async throws // ok+}++distributed actor Greeter: GreeterProtocol { +  distributed func greet(name: String) async throws { ... } // ok (String is codable, async + throws)+}+```++It is possible to declare "distributed actor protocols", which are protocols which also require the `DistributedActor` protocol. Such protocols can only be conformed to by distributed actors, and can only contain `nonisolated` or `distributed` functions, and effectively can be thought of as form of "service API definitions", where the service is such distributed actor:++```swift+protocol DistributedGreeter: DistributedActor {+  distributed func greet(name: String)+}+```++As usual with distributed method declarations, the `async` and `throws` effects are implicit, yet may be specified explicitly as well.++##### Default Conformances++The `Equatable` and `Hashable` protocols from the Swift standard library require conformers to provide a way to distinguish between equivalent instances. When value types like structs and enums are declared to conform to these protocols, the `==` and `hash` witnesses are automatically synthesized, if the programmer does not specify them.++For distributed actors, any realistic `ActorTransport` will require conformance to `Equatable` and `Hashable`. While distributed actors are reference types, they are designed to have a stable, sharable identifier associated with each instance. Thus, Swift will automatically require that distributed actors conform to these protocols, and provide the following witnesses:++```swift+extension DistributedActor: Equatable {+  public static func ==(lhs: Self, rhs: Self) -> Bool {+    lhs.id == rhs.id+  }+}++extension DistributedActor: Hashable { +  nonisolated public func hash(into hasher: inout Hasher) {+    self.id.hash(into: &hasher)+  }+}+```++It is difficult to imagine any other witnesses for these protocols, because any accesses to the actor's internal state would require an asynchronous operation.++#### Distributed Methods++Distributed methods are declared by writing the `distributed` keyword in the place of a declaration modifier, under the `actor-isolation-modifier` production rule as specified by [the grammar in TSPL](https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_declaration-modifiers). Only methods can use `distributed` as a declaration modifier, and no order is specified this modifier.+A `distributed actor` type, extensions of such a type, and `DistributedActor` inherting protocols are the only places where distributed method declarations are allowed. This is because, in order to implement a distributed method, a transport and identity must be associated with the values carrying the method. Distributed methods can synchronously refer to any of the state isolated to the distributed actor instance.++As a consequence of the request-response nature of distributed methods, `inout` parameters are not supported. While subscripts are similar to methods, they are not allowed for a distributed actor. A subscript's usefulness is strongly limited by both their lack of support for being `distributed` (e.g., could only support read-only subscripts, because no coroutine-style accessors) and their lightweight syntax can lead to the same problems as properties.+++### Distributed Actor lifecycle++The lifecycle of a distributed actor is important to consider, because it is a key part of how location transparency is achieved.++#### Local Initialization++All user-defined designated initializers of a distributed actor can be collectively referred to as "local" initializers. All local initializers of a distributed actor must accept exactly one `ActorTransport` parameter. This `ActorTransport` parameter is implicitly used to fulfil an important contract with the transport and the actor instance itself. Namely, all distributed actor instances must be assigned an `ActorIdentity`, which is provided by the transport. In addition, once fully initialized, the actor must inform the transport that it is ready to receive messages. Conceptually, these steps can be described as:++```swift+distributed actor DA {+  init(..., transport: ActorTransport) {+    // Step 1: Try to set-up the transport and identity.+    self._transport = transport+    self._id = AnyActorIdentity(transport.assignIdentity(Self.self))+    +    // Step 2: User code is executed, which may fail, access the identity, etc.+    +    // Step 3: The initializer did not fail; notify the transport.+    transport.actorReady(self)+  }+}+```++If no user-defined designated initializer is provided, a default initializer is synthesized that requires a named parameter `transport` of type `ActorTransport`, i.e., `init(transport:)`. The access level of the synthesized init is the same as the distributed actor declaration (i.e. for an internal distributed actor, the init is internal, and public for a public one). As with other nominal types, this initializer is only synthesized by the compiler if all stored properties can be default-initialized.++```swift+distributed actor DA {+  // synthesized: init(transport: ActorTransport) { ... }+}+```++#### Remote Resolution++There is no representation of a remote initializer for a distributed actor, because a remote instance has *conceptually* already been initialized. Thanks to location transparency, a remote instance is not even guaranteed to exist until it is actually needed. This is an important implementation detail that enables efficient implementations of distributed systems. To make this more concrete, consider a simplified version of the static `resolve` function that is synthesized for all distribtued actors:++```swift+distributed actor DA {  +  // Pseudocode for the implementation of actor resolution.+  static func resolve(_ identity: ActorIdentity, using transport: ActorTransport) throws -> Self {+    if let instance = try transport.resolve(identity, as: Self.self) {+      return instance+    } else {+      // no local instance was found, resolve as remote actor (i.e. create a "proxy")+      return __runtimeMagicToAllocateProxyActor(...)+    }+  }+}+```++It is important to notice that, when the transport determines that the identity is not local, the static `resolve` only performs a memory allocation for a "proxy" representing a remote instance - no memory has to be allocated for stored properties of such remote actor, because they will never be accessed, making remote references extremely memory efficient (!). Thus, resolving an actor does not necessarily perform an inter-process action, even if the identity is for a remote instance. Inter-process actions are only guaranteed when calling a distributed method on a remote instance. Nevertheless, a resolve action may throw an error if the transport decides that it cannot resolve the passed in identity, e.g., because it is for a different transport.++In addition, it is up to the transport to determine whether an identity is local or not. This is how concepts like *virtual actors* may be implemented. While discussing the semantics of virtual actors is out of scope for this proposal, the ability to possibly support them in the future is important.++A distributed actor's static resolve function, and the related `ActorTransport.resolve` function, are not offered as an `async` function, because the `Codable` infrastructure is not currently async-ready. But, most kinds of transports should not require an asynchronous `resolve`, because actor resolution is typically implemented using local knowledge.++#### Deinitialization++Local distributed actors also implicitly *resign* their identity to the transport once they are deinitialized. Specifically, the transport's `resignIdentity` function is called after all user code, including user-defined `defer` statements, have executed.++```swift+deinit { +  // Step 1: User-defined deinitialization code runs, including `defer` statements+  +  // Step 2: local actor resigns its identity; +  // The transport may release any resources associated with this identity, e.g. connections etc.+  transport.resignIdentity(self.id)+}+```++Transports are generally expected to *not* hold strong references to actors they manage. Otherwise, such an actor may never be deinitialized! If a transport wishes to hold the only strong reference to a distributed actor, care must be taken to drop the actor reference before the transport is deallocated.++### Actor Transports++Swift users will need to provide their own implementation of an `ActorTransport`, in order to use distributed actors. It is expected that most users will use an existing library that implements the desired kind of transport infrastructure. But, users are free to implement their own `ActorTransport` according to the following protocol:++```swift+protocol ActorTransport: Sendable {++  // ==== ---------------------------------------------------------------------+  // - MARK: Resolving actors by identity++  func decodeIdentity(from decoder: Decoder) throws -> AnyActorIdentity++  func resolve<Act>(_ identity: AnyActorIdentity, as actorType: Act.Type) throws -> Act?+      where Act: DistributedActor++  // ==== ---------------------------------------------------------------------+  // - MARK: Actor Lifecycle++  func assignIdentity<Act>(_ actorType: Act.Type) -> Act.ID+      where Act: DistributedActor++  func actorReady<Act>(_ actor: Act) +      where Act: DistributedActor++  func resignIdentity(_ id: AnyActorIdentity)++}+```++At a high-level, a transport has two major responsibilities:++- **Lifecycle management:** creating and resolving actor identities, which are used by the Swift runtime to construct distributed actor instances.+- **Messaging:** perform all message dispatch and handling on behalf of a distributed actor it manages.++Distributed actor lifecycle was already discussed in the previous section, so now let us focus on how messaging is implemented by a transport.++#### Message Handling++> This section discusses the messaging implementation on a rather high-level, sufficient to understand the mechanism, however without diving deep into the implementation approach. Those curious about the implementation details should see the [Runtime implementation details](#runtime-implementation-details) section of the Appendix.++A distributed actor depends on its transport for all of its message handling. Outbound and inbound messages all pass through the actor transport, which must handle all serialization and networking concerns for these messages.++##### Outbound messages++Outbound messaging is what occurs when a distributed function is invoked on a _remote_ instance of a distributed actor, for example like this:++```swift+let greeter = Greeter.resolve(remoteID, using: transport)+let reply: String = try await greeter.greet("Hello there!")+```++Since the greeter is remote, the invocation will actually call the `_remote_greet(_:)` function which was synthesized by the compiler.++This function by itself is only a stub, and will crash unless it is replaced with a transport specific implementation. Specifically, transport frameworks are expected to source generate (using a SwiftPM plugin), the following declaration to replace the remote function:++```swift+// specific transport source generated (until we have the ability to avoid source-gen here)+extension Greeter { +  // TODO: introduce `distributed` specific annotation, e.g. @remoteReplacement(for:greet(_:))+  @_dynamicReplacement(for: _remote_greet(_:))+  nonisolated func _cluster_greet(_ greeting: String) async throws -> String { +    // Step 1: Form a message+    let message: _Message = _Message.greet(greeting)+    +    // Step 2: Dispatch the message to the underlying transport+    // (simplified, it would be possible to check for various known/compatible transports)+    return try await (self.transport as! KnownTransport).send(message, to: self.id)+  }+}+```++The `@_dynamicReplacement` allows developers to separately develop distributed actor _libraries_ from their end-user's code. This is possible because a distributed actor does not have to implement the `_remote_X` cases during testing and initial development. Only once the developer wants to make use of its distributed nature, will they have to provide a `_remote_X` function implementation for each distributed method `X`.++> **NOTE:** The `@_dynamicReplacement(for:)` will be replaced with a distributed actor specific attribute. The proposal and use of dynamic member replacement reflects the current state of the implementation. +>+> We are likely going to propose some form of `@distributedRemoteImplementation(for:greet(_:))` in order to completely hide the existence of the `_remote` functions from users. The implementation mechanism would be the same, by making use of dynamic member replacement. This change will be introduced in an upcoming revision of the proposal.++Notice that the `_remote_greet` definition is `nonisolated`, out of necessity: a remote instance is a proxy that has not obtained exclusive access to isolated state. Thus, it must tell the transport to forward the message to `self.id`, because that is the identity of the intended recipient.++This separate definition of `_remote` stubs allows developers to test entire modules that contain only distributed actor definitions _without_ any specific actor transport, e.g., in a module called `CASPaxos`. Then, developers can test `CASPaxos` a module called `CASPaxosTests`, which uses a `TestTransport`. Finally, developers can put `CASPaxos` into production in a module called `MySpaceShipApp` that depends only on `CASPaxos` and `SpaceActorTransport`. This allows the `MySpaceShipApp` to use the general and reusable `CASPaxos` distributed actor implementation, but with it's own _specific_ transport, while other modules can use it with some other transport.++Since the transport is called within the same task as the executing replaced function, the transport may also pick up any task-local values to enrich the message being sent over the wire, _etc_. We discuss this idea further in [Swift Distributed Tracing integration](#swift-distributed-tracing-integration) of the Appendix.++> **NOTE:** Currently, programmers need to rely on separate source generation to synthesize `_remote` stubs, because Swift does not have a sufficiently advanced meta-programming and type-system capability to derive them from a method signature. We discuss removing this source-generation approach with meta-programming in [Future Directions](#future-directions) of the Appendix.++##### Inbound messages++Once the remote side has received a message, e.g. by reading it from an incoming network request, the transport has to look-up and deliver the message to the actor. ++This is where the transport's specific implementation of resolving an actor comes into play. It will receive the `AnyActorIdentity` for which the message was intended (or whichever way it communicates this), and must locate the specific *local* instance identified by this identity. Usually this will be implemented by either some lookup table inside the transport, where it maintains a mapping of known identities to specific instances. Pseudo code of receiving a message might look like this:++```swift+// pseudo-code for receiving a message in the transport.++// Step 1: Receive remove envelope which contains recipient and actual message+let bytes = network.read(...)+let envelope = try decoder.decode(Envelope.self, from: bytes)++// Step 2: locate the local actor instance; this is the same as `resolve(...)`+let reply: ResponseData+if let anyDistributedActor = knownActors[envelope.recipientID] {+  // Step 3: Decode the specific SpecificActor._Message and _receive(message:) it+  let message = anyDistributedActor._decodeMessage(envelope, decoder)+  // Step 4: Invoke the actor's _receive which applies the message as function invocation+  reply = try await anyDistributedActor._receive(message)+} else {+  // recipient not found, send back error response+  reply = someErrorMessage+}++// Step 5: Send back the reply (if needed)+```++Which eventually calls into the source generated `_receive` function of a distributed actor like this:++```swift+extension Greeter { +  func _receive(message: Self._Message, ...) async throws -> ResponseData {+    assert(_isLocalDistributedActor(self))+    +    switch message {+      case .greet(let greeting):+        let reply = try await self.greet(greeting)+        return encode(reply)+      // ... +    }+  }+}+```++The specific `encoder`/`decoder` choice is left up to the specific transport that the actor was created with. It is legal for a distributed actor transport to impose certain limitations, e.g. on message size or other arbitrary runtime checks when forming and sending remote messages. Distributed actor users may need to consult the documentation of such transport to understand those limitations. It is a non-goal to abstract away such limitations in the language model. There always will be real-world limitations that transports are limited by, and they are free to express and require them however they see fit (including throwing if e.g. the serialized message would be too large etc.)++#### Actor Identity++A distributed actor's identity is defined by its `id` property which stores an instance of the `ActorIdentity` type. ++A distributed actor's identity is automatically assigned and managed during its initialization. Refer to the section on [Distributed Actor initialization](#distributed-actor-initialization) for more details.++The specific implementation of the identity is left up to the transport that assigns it, and therefore may be as small as an `Int` or as complex as a full `URI` representing the actor's location in a cluster. As the identity is used to implement a number of protocol requirements, it must conform to a number of them as well, most notably it should be `Sendable`, `Codable`, and `Hashable`:++```swift+public protocol ActorIdentity: Sendable, Codable, Hashable {}+```++The actual `id` property of the distributed actor is a `nonisolated` computed property which is synthesized by the compiler and points at the identity assigned to the actor during the initialization (or resolve process). ++The property uses a type-erased struct to store the identity, because otherwise `Hashable`'s `Self` type requirements would prevent using `DistributedActor` bound protocols as existentials, which is a crucial use-case that we are going to implement in the near future.++Specific transports, depending on their use-cases and needs, may implement the identity protocol slightly differently. For example, they might implement the identity using a simple numeric identifier, or URI-like scheme. Implementing an identifier is simple, because a `struct` type gets all the necessary implementation pieces synthesized automatically, so it can be as simple as defining:++```swift+// e.g. some "Net Actors" library may define the identity as:+struct NetActorAddress: ActorIdentity { +  let uri: String+}+```++Comparing actor identities is sufficient to know if the pointed-at actors are the logically "the same" actor or different ones. An actor is inherently bound to a transport that it was created with, and as such, to the address assigned to it. This means that the same actor should be pointed at using the same address type, so cross type comparisons are not a big concern.++We also offer the type-eraser `AnyActorIdentity` which is used to _store_ arbitrary actor identities, and is useful for e.g. using it as keys in a dictionary:++```swift+public struct AnyActorIdentity: ActorIdentity {+  public init<ID>(_ identity: ID) where ID: ActorIdentity { ... }+    // ... +  }+}+```++#### Transporting Errors++A transport _may_ attempt to transport errors back to the caller if it is able to encode/decode them.++Because the errors thrown by a `distributed func` are not typed, it is impossible to enforce at compile time that an error is always `Codable`. Transports may perform a best effort attempt to encode/decode errors, e.g. perhaps by encoding just the error type name rather than the entire error object and send it back to the caller where it would be thrown as an ActorTransportError subtype. Some transports may attempt encoding the entire Error object _if it was `Codable`_, however to do so securely, such types would also need to be registered in safe-lists for serialization etc. This isn't a topic we're exploring in depth in this proposal (because it is a transport implementation concern), but have explored and thought about already.++As usual with today's Swift, it is possible to express strongly typed errors by using `Result<User, InvalidPassword>` as it allows for typed handling of such errors as well as automatically enforcing that the returned error type is also `Codable` and thus possible to encode and transport back to the caller. This is a good idea also because it forces developers to consider if an error really should be encoded or not (perhaps it contains large amounts of data, and a different representation of the error would be better suited for the distributed function).++The exact errors thrown by a distributed function depends on the underlying transport. Generally one should expect some form of `struct BestTransportError: ActorTransportError { ... }` to be thrown by a distributed function if transport errors occur--the exact semantics of those throws are intentionally left up to specific transports to document when and how to expect errors to be thrown.++To provide more tangible examples, why a transport may want to throw even if the called function does not, consider the following:++```swift+// Node A+distributed actor Failer { +  distributed func letItCrash() { fatalError() } +}+```++```swift+// Node B+let failer: Failer = ... ++// could throw transport-specific error, +// e.g. "ClusterTransportError.terminated(actor:node:existenceConfirmed:)"+try await failer.letItCrash() +```++This allows transports to implement failure detection mechanisms, which are tailored to the specific underlying transport, e.g. for clustered applications one could make use of Swift Cluster Membership's [SWIM Failure Detector](https://www.github.com/apple/swift-cluster-membership), while for IPC mechanisms such as XPC more process-aware implementations can be provided. The exact guarantees and semantics of detecting failure will of course differ between those transports, which is why the transport must define how it handles those situations, while the language feature of distributed actors _must not_ define it any more specifically than discussed in this section.++### Sharing and Discovery++#### Distributed Actors are `Codable`++Distributed actors are `Codable` and are represented as their _actor identity_ in their encoded form.++In order to be true to the actor model's idea of freely shareable references, regardless of their location (see [*location transparency*](#location-transparency)), we need to be able to pass distributed actor references to other--potentially remote--distributed actors.++This implies that distributed actors must be `Codable`. However, the way that encoding and decoding is to be implemented differs tremendously between _data_ and active entities such as _actors_. Specifically, it is not desirable to serialize the actor's state - it is after all what is isolated from the outside world and from external access.++The `DistributedActor` protocol also conforms to `Codable`. As it does not make sense to encode/decode "the actor", per se, the actor's encoding is specialized to what it actually intends to express: encoding an identity, that can be resolved on a remote node, such that the remote node can contact this actor. This is exactly what the `ActorIdentity` is used for, and thankfully it is an immutable property of each actor, so the synthesis of `Codable` of a distributed actor boils down to encoding its identity:++```swift+extension DistributedActor: Encodable {+  nonisolated func encode(to encoder: Encoder) throws { +    var container = encoder.singleValueContainer()+    try container.encode(self.actorAddress)+  }+}+```++Decoding is slightly more involved, because it must be triggered from _within_ an existing transport. This makes sense, since it is the transport's internal logic which will receive the network bytes, form a message and then turn to decode it into a real message representation before it can deliver it to the decoded and resolved recipient. ++In order for decoding of an Distributed Actor to work on every layer of such decoding process, a special `CodingUserInfoKey.distributedActorTransport` is used to store the actor transport in the Decoder's `userInfo` property, such that it may be accessed by any decoding step in a deep hierarchy of encoded values. If a distributed actor reference was sent as part of a message, this means that it's `init(from:)` will be invoked with the actor transport present. ++The default synthesized decoding conformance can therefore automatically, without any additional user intervention, decode itself when being decoded from the context of any actor transport. The synthesized initializer looks roughly like this:++```swift+extension DistributedActor: Decodable {+  init(from decoder: Decoder) throws {+    guard let transport = self.userInfo[.transport] as? ActorTransport else {+      throw DistributedActorDecodingError.missingTransportUserInfo(Self.self)+    }+    let container = try decoder.singleValueContainer()++    let identity = try container.decode(AnyActorIdentity.self)+    self = try Self(resolve: identity, using: transport)+  }+}+```++During decoding of such reference, the transport gets called again and shall attempt to `resolve` the actor's identity. If it is a local actor known to the transport, it will return its reference. If it is a remote actor, it will return a proxy instance pointing at this identity. If the identity is illegal or unrecognized by the transport, this operation will throw and fail decoding the message.++> **Note:** In general, it is not currently possible to express the initializer above, which assigns to `self`, in Swift. The limitation is [mostly artificial](https://forums.swift.org/t/allow-self-x-in-class-convenience-initializers/15924) and could be supported in Swift. So, for the purpose of this proposal, the limitation is lifted specifically for a distributed actor's Decodable initializers. As a separate proposal, providing this capability for any class or actor type would be ideal.+To show an example of what this looks like in practice, we might implement an actor cluster transport, where the actor identities are a form of URIs, uniquely identifying the actor instance, and as such encoding an actor turns it into `"[transport-name]://system@10.0.0.1:7337/Greeter#349785`, or using some other encoding scheme.++It is tremendously important to allow passing actor references around, across distributed boundaries, because unlike local-only actors, distributed actors can not rely on passing a closure to another actor to implement "call me later" style patterns. This is also what enables the familiar "delegate" style patterns to be adopted and made use of using distributed actors!++Such "call me later"-patterns must be expressed by passing the `self` of a distributed actor (potentially as a `DistributedActor` bound protocol) to another distributed actor, such that it may invoke it whenever necessary. E.g. publish/subscribe patterns implemented using distributed patterns need this capability:++```swift+distributed actor PubSubPublisher {+  var subs: Set<AnySubscriber<String>> = []+  +  /// Subscribe a new distributed actor/subscriber to this publisher+  distributed func subscribe<S>(subscriber: subscriber)+    where S: SimpleSubscriber, S.Value == String {+    subs.insert(AnySubscriber(subscriber))+  }+  +  /// Emit a value to all subscribed distributed actors+  distributed func emit(_ value: String) async throws { +    for s in subs {+      try await s.onNext(value)+    }+  }+}+```++```swift+protocol SimpleSubscriber: DistributedActor {+  associatedtype Value: Codable+  distributed func onNext(_ value: Value)+}++distributed actor PubSubSubscriber: Subscriber { +	typealias Value = String+  +  let publisher: PubSubPublisher = ...+  +  func start() async throws { +    try await publisher.subscribe(self) // `self` is safely encoded and sent remotely to the publisher+  }+  +  /// Invoked every time the publisher has some value to emit()+  func onNext(_ value: Value) { +    print("Received \(value) from \(publisher)!")+  }+}+```++The above snippet showcases that with distributed actors it is pretty simple to implement a small _distributed_ pub-sub style publisher, and of course this ease of development extends to other distributed systems issues. The fundamental building blocks being a natural fit for distribution that "just click" are of tremendous value to the entire programming style with distributed actors. Of course a real implementation would be more sophisticated in its implementation, but it is a joy to look at how distributed actors make mundane and otherwise difficult distributed programming tasks simple and understandable.++This allows us to pass actors as references across distributed boundaries:++```swift+distributed actor Person {+  distributed func greet(_ greeting: String) async throws {+    log.info("I was greeted: '\(greeting)' yay!")+  }+}++distributed actor Greeter { +  var greeting: String = "Hello, there!"+  +  distributed func greet(person: Person) async throws {+    try await person.greet("\(greeting)!")+  }+}+```++#### Discovering Existing Instances++As discussed, identities are crucial to locate and resolve an opaque identity into a real and "live" actor reference that we can send messages through.++However, how do we communicate an identity of one actor from one node to another if to identify _any distributed actor_ actor we need to know their identity to begin with? This immediately ends up in a "catch-22" situation, where in order to share an identity with another actor, we need to know _that_ actor's identity, but we can't know it, since we were not able to communicate with it yet!++Luckily, this situation is not uncommon and has established solutions that generally speaking are forms of _service discovery_. Swift already offers a general purpose service discovery library with [swift-service-discovery](https://github.com/apple/swift-service-discovery), however its focus is very generic and all about services, which means that for a given well-known service name e.g. "HelloCluster", we're able to lookup which _nodes_ are part of it, and therefore we should attempt connecting to them. Implementations of service discovery could be using DNS, specific key-value stores, or kubernetes APIs to locate pods within a cluster. ++This is great, and solves the issue of locating _nodes_ of a cluster, however we also need to be able to locate specific _distributed actors_, that e.g. implement some specific protocol. For example, we would like to, regardless of their location locate all `Greeter` actors in our distributed actor system. We can use the well-known type name Greeter as key in the lookup, or we could additionally qualify it with some identifier for example only to find those `Greeter` actors which use the language `pl` (for Polish).++In practice, _how_ such lookups are performed is highly tied to the underlying transport, and thus the transport library should provide a specific pattern to perform those lookups. We call this pattern the `Receptionist`, and it may take the following protocol shape:++```swift+protocol Receptionist: DistributedActor { +    @discardableResult+    public func register<Guest>(+        _ guest: Guest,+        with key: Reception.Key<Guest>,+        replyTo: ActorRef<>? = nil+    ) async -> Reception.Registered<Guest> +      where Guest: ReceptionistGuest++    distributed func lookup<Guest>(+        _ key: Reception.Key<Guest>+    ) async -> Reception.Listing<Guest> +      where Guest: DistributedActor++  // more convenience functions may exist ...+}+```++Such receptionist is a normal distributed actor, which may be resolved on the used transport and performs the actor "discovery" on our behalf.++In practice, this means that locating all greeters in a system boils down to:++```swift+guard let anyGreeter =+  try await Receptionist.resolve(transport)+    .lookup(Greeter.self).first else {+  print("No Greeter discovered!")

Right thanks :+1:

ktoso

comment created time in 20 hours

PullRequestReviewEvent

Pull request review commentapple/swift-evolution

[Pitch] Distributed Actors

+# Distributed Actors++* Proposal: [SE-NNNN](NNNN-distributed-actors.md)+* Authors: [Konrad 'ktoso' Malawski](https://github.com/ktoso), [Dario Rexin](https://github.com/drexin), [Doug Gregor](https://github.com/DougGregor), [Tomer Doron](https://github.com/tomerd), [Kavon Farvardin](https://github.com/kavon)+* Review Manager: TBD+* Status: **Implementation in progress**+* Implementation: +  * Partially available in [recent `main` toolchain snapshots](https://swift.org/download/#snapshots) behind the `-enable-experimental-distributed` feature flag. +  * This flag also implicitly enables `-enable-experimental-concurrency`.+* Sample app:+  * A sample app, showcasing how the various "pieces" work together is available here:+    [https://github.com/apple/swift-sample-distributed-actors-transport](https://github.com/apple/swift-sample-distributed-actors-transport)++## Table of Contents+++<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->++<!-- code_chunk_output -->++- [Distributed Actors](#distributed-actors)+  - [Table of Contents](#table-of-contents)+  - [Introduction](#introduction)+  - [Motivation](#motivation)+  - [Proposed solution](#proposed-solution)+    - [Distributed actors](#distributed-actors-1)+      - [Properties](#properties)+      - [Methods](#methods)+      - [Initialization](#initialization)+    - [RPC Example](#rpc-example)+  - [Detailed design](#detailed-design)+    - [Location Transparency](#location-transparency)+    - [Typechecking Distributed Actors](#typechecking-distributed-actors)+      - [Protocol Conformances](#protocol-conformances)+        - [Default Conformances](#default-conformances)+      - [Distributed Methods](#distributed-methods)+    - [Distributed Actor lifecycle](#distributed-actor-lifecycle)+      - [Local Initialization](#local-initialization)+      - [Remote Resolution](#remote-resolution)+      - [Deinitialization](#deinitialization)+    - [Actor Transports](#actor-transports)+      - [Actor Identity](#actor-identity)+      - [Transporting Errors](#transporting-errors)+    - [Sharing and Discovery](#sharing-and-discovery)+      - [Distributed Actors are `Codable`](#distributed-actors-are-codable)+      - [Discovering Existing Instances](#discovering-existing-instances)+  - [Alternatives Considered](#alternatives-considered)+    - [Creating only a library and/or tool](#creating-only-a-library-andor-tool)+    - [Special Actor spawning APIs](#special-actor-spawning-apis)+      - [Explicit `spawn(transport)` keyword-based API](#explicit-spawntransport-keyword-based-api)+      - [Global eagerly initialized transport](#global-eagerly-initialized-transport)+      - [Directly adopt Akka-style Actors References `ActorRef<Message>`](#directly-adopt-akka-style-actors-references-actorrefmessage)+  - [Acknowledgments & Prior Art](#acknowledgments-prior-art)+  - [Source compatibility](#source-compatibility)+  - [Effect on ABI stability](#effect-on-abi-stability)+  - [Effect on API resilience](#effect-on-api-resilience)+  - [Changelog](#changelog)+- [Appendix](#appendix)+  - [Runtime implementation details](#runtime-implementation-details)+      - [Remote `distributed actor` instance allocation](#remote-distributed-actor-instance-allocation)+      - [`distributed func` internals](#distributed-func-internals)+  - [Future Directions](#future-directions)+      - [The `AnyActor` marker protocol](#the-anyactor-marker-protocol)+    - [Storing and requiring more specific Transport types](#storing-and-requiring-more-specific-transport-types)+    - [Ability to customize parameter/return type requirements](#ability-to-customize-parameterreturn-type-requirements)+    - [Resolving DistributedActor bound protocols](#resolving-distributedactor-bound-protocols)+    - [Synthesis of `_remote` and well-defined `Envelope<Message>` functions](#synthesis-of-_remote-and-well-defined-envelopemessage-functions)+    - [Support for `AsyncSequence`](#support-for-asyncsequence)+    - [Ability to hardcode actors to specific shared transport](#ability-to-hardcode-actors-to-specific-shared-transport)+    - [Actor Supervision](#actor-supervision)+  - [Related Work](#related-work)+    - [Swift Distributed Tracing integration](#swift-distributed-tracing-integration)+    - [Distributed `Deadline` propagation](#distributed-deadline-propagation)+    - [Potential Transport Candidates](#potential-transport-candidates)+  - [Background](#background)++<!-- /code_chunk_output -->++++## Introduction++Swift's [actors](https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md) are a relatively new building block for creating concurrent programs. The mutable state isolated by actors can only be interacted with by one task at any given time, eliminating a whole class of data races. But, the general [actor model](https://en.wikipedia.org/wiki/Actor_model) works equally well for distributed systems too.++This proposal introduces *distributed actors*, which are an extension of regular Swift actors that allows developers to take full advantage of the general actor model of computation. Distributed actors allow developers to scale their actor-based programs beyond a single process or node, without having to learn many new concepts.++Unlike regular actors, distributed actors take advantage of *[location transparency](https://en.wikipedia.org/wiki/Location_transparency)*, which relies on a robust, sharable handle (or name) for each actor instance. These handles can reference actor instances located in a different program process, which may even be running on a different machine that is accessible over a network. After resolving an actor handle to a specific instance located _somewhere_, the basic interactions with the actor instance closely mirrors that of regular actors. Distributed actors abstract over the communication mechanism that enables location transparency, so that the implementation can be extended to different domains. ++## Motivation++Distributed systems are not just for high-performance computing. Many of the programs written today are part of a distributed system. If your program uses *any* kind of [inter-process communication](https://en.wikipedia.org/wiki/Inter-process_communication) (IPC), i.e., it communicates with entities outside of its process on the same or a different machine, then it can be viewed as a distributed system. Real-world examples include networked applications, such as games, chat or media applications.++Distributed systems are pervasive, yet they require significant effort to implement in Swift. Consider this simple example of code that establishes a connection with a server and listens for messages that trigger some action:++```swift+import Foundation++actor Counter {+  var count: Int = 0+  func increment() {+    count += 1+  }+  func get() -> Int { return count }+}++class ConnectionManager {+  var connection: URLSessionWebSocketTask+  var state: Counter++  // Initialize a connection with the other server+  // and begin listening for messages.+  init?(_ serverURL: URLRequest, sharing counter: Counter) {+    state = counter+    connection = URLSession.shared.webSocketTask(with: serverURL)+    guard connection.error != nil else {+      return nil+    }+    listenLoop()+  }++  private func listenLoop() {+    Task.detached {+      switch try? await self.connection.receive() {+        case .some(let message):+          // NOTE: skip message deserialization; assume 1 message kind!+          await self.state.increment()  // perform action on local instance+          self.listenLoop() // listen for the next connection.+        default:+          return // end listen loop+      }+    }+  }++  deinit {+    connection.cancel(with: .normalClosure, reason: nil)+  }+}++let counter = Counter() // some shared actor instance++// Start the connection!+let serverReq = URLRequest(url: URL(string: "http://example.com:1337")!)+let c = ConnectionManager(serverReq, sharing: counter)+```++This is a barebones implementation of a unidirectional remote-procedure call (RPC), where the code above corresponds to the receiver's side that implements the requests, but does not perform responses. In this example, notice the reliance on a regular actor to synchronize accesses to its state, because the state can be accessed on the reciever's side too. While the sender-side code is omitted, it is already apparent that the basic pieces of an RPC-based distributed program involves:++1. Resolving / maintaining a connection.+2. Serializing and deserializing messages.+3. Servicing requests.++ The distributed actors in this proposal are designed to model an actor whose instance may be located in another program process. This means that the sender side can interact with the `Counter`'s state without the extra boilerplate. Specifically, distributed actors abstract over the actor's identifier and the communication transport, such as TCP/UDP, WebSocket or cross-process transports like XPC. This abstraction allows distributed code to focus on the implementation of the distributed actor's operations, using the familiar syntax and semantics of Swift's regular actors, and without needing to implement the communication in an ad-hoc manner. The same definition of a distributed actor is compatible with various identity and transport implementations, instances of which can even co-exist simultaneously in the same process.++## Proposed solution++There are several new concepts introduced by this proposal, which will be described in this section.++### Distributed actors++Distributed actors are a special flavor of the actor type that enforces additional rules on the type and its values, in order to achieve location transparency. They are written by prepending `distributed` to an actor declaration, like so:++```swift+distributed actor Player {+  let name: String+  var score: Int+  var teammates: [Player]+}+```++Distributed actors adopt the same isolation rules of regular actors, but because any instance may actually refer to an actor in another process (i.e., location transparency), extra rules are applied.++#### Properties+Developers should think carefully about operations that cross into the actor's isolation domain, because the cost of each operation can be expensive (e.g., if the actor is on a machine across the internet). Properties make it very easy to accidentially make multiple round-trips:++```swift+func example1(p: Player) async throws -> (String, Int) {+  try await (p.name, p.score) // ❌ might make two slow round-trips to `p`+}+```++Instead, methods are strongly encourged so that accesses can be batched.+Thus, access to a distributed actor's properties (stored and computed) from outside of the actor's isolation are forbidden. In addition, properties cannot be `nonisolated` or participate in a key-path.++#### Methods++Regular methods isolated to the distributed actor are not accessible from outside of the actor's isolation context. A new kind of method declaration, called a *distributed method*, are the only kind of isolated members that can be accessed from outside of a distributed actor. Nonisolated methods can be defined as usual, but a distributed method cannot be marked `nonisolated`. A distributed method is defined within a distributed actor by writing `distributed` in front of the method's declaration:++```swift+extension Player {+  distributed func addTeammate(p: Player) {+    guard !haveTeammate(p) else {+      return+    }+    teammates.append(p)+  }++  func haveTeammate(p: Player) -> Bool {+    return teammates.contains(p)+  }+}++func example2(a: Player, b: Player) async throws {+  a.haveTeammate(b) // ❌ error, function not `distributed`+  +  try await a.addTeammate(b)  // ✅ OK. distributed actors are Codable!+}+```++In addition to conforming to `Sendable`, a distributed function's parameters and its return type are all required to conform to the [`Codable` protocol](https://developer.apple.com/documentation/swift/codable). A codable value supports serialization and deserialization, which is nessecary in order to maintain location transparency. For example, servicing a method call on a distributed actor instance can involve sending the arguments to another process and awaiting a response.++Like a regular actor method, an expression that calls a distributed function is  treated as `async` from outside of the actor's isoalation. But for a distributed actor, such calls are _also_ treated as `throws` when outside of the actor's isolation, because a request to call a distributed method is not guaranteed to recieve a response. The underlying process that hosts the distributed actor instance may be on another machine that crashed, or a connection may be lost, etc. To help make this clear, consider the following example, which contains only the nessecary `try` and `await` expressions:++```swift+distributed actor Greeter {+  distributed func englishGreeting() -> String { "Hello!" }+  distributed func japaneseGreeting() throws -> String { "こんにちは!" }+  distributed func germanGreeting() async -> String { "Servus!" }+  distributed func polishGreeting() async throws -> String { "Cześć!" }+  +  func inside() async throws { +    _ = self.englishGreeting()+    _ = try self.japaneseGreeting()+    _ = await self.germanGreeting()+    _ = try await self.polishGreeting()+  }+} // end of Greeter++func outside(greeter: Greeter) async throws { +  _ = try await greeter.englishGreeting()+  _ = try await greeter.japaneseGreeting()+  _ = try await greeter.germanGreeting()+  _ = try await greeter.polishGreeting()+}+```++Errors thrown by the underlying transport due to connection or messaging problems must conform to the `ActorTransportError` protocol. A distributed function can also be explicitly marked as `throws`, as shown above, but the underlying transport is responsible for determining how to forward any thrown errors, since errors thrown by a distributed function do _not_ have to be `Codable`.++One benefit of explicitly marking distributed functions with the `distributed` keyword that it makes clear to the programmer (and tools, such as IDEs) where networking costs are to be expected when using a distributed actor. This is an improvement over today's world, where any function might ultimately perform network calls, and we are not able to easily notice them, for example, in the middle of a tight loop.++#### Initialization+Distributed actors and functions are largely a set of a rules that enforce location transparency. In order for distributed actors to actually perform any remote communication, it is necessary to initialize them with an actual implementation of an `ActorTransport`, which is a protocol that is implementable by library writers. A *transport* handles all of the connection details that underpin distributed actors. An actor transport can take advantage of any networking, cross-process, or even in-memory approach to communicate between instances, as long as it conforms to the protocol correctly.++There are two ways to initialize a distributed actor, but in any case, a distributed actor is always associated with some `ActorTransport`. To create a new **local** instance of a distributed actor, call the actor's `init` as usual. All non-delegating initializers, which fully-initialize the distributed actor, are required to accept exactly one argument that conforms to `ActorTransport`:++```swift+distributed actor Capybara { +  var name: String+  +  // ✅ ok. exactly one transport param for non-delegating initializer.+  init(named name: String, using: ActorTransport) {+    self.name = name+  }++  // ✅ ok. no transport param, but it's a delegating initializer.+  convenience init() {+    self.init(named: "Happybara", using: Defaults.IPoAC)+  }++  // ❌ error, too many transport params for a non-delegating init.+  init(main: ActorTransport, alt: ActorTransport) {+    self.name = "Sleepybara"+  }+  +  // ❌ error, no transports for a non-delegating init.+  init(withName name: String) {+    self.name = name+  }+}+```++Swift will automatically associate a `Capybara` instance with the transport passed to `init(named:using:)`, without the programmer explicitly using the transport argument. This is done, instead of requiring a specific `let`-bound property to be initialized, because the `ActorTransport` is specially stored in the instance, depending on whether it represents a remote or local instance of the actor.++A **remote** instance can be only be resolved if the programmer also has the instance's `ActorIdentity`. An `ActorIdentity` is a protocol that specifies the minimum capabilities of a durable, unique identifier associated with a distributed actor instance. All distributed actors have two computed properties that are accessible from anywhere, and a static `resolve` function:++```swift+public protocol DistributedActor: Sendable, Codable, Identifiable, Hashable {++  /// 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.+  ///+  /// Conformance to this requirement is synthesized automatically for any+  /// `distributed actor` declaration.+  /*special*/ var id: AnyActorIdentity { get }++  /// Transport responsible for managing the lifecycle and messaging aspects of this actor.+  /*special*/ var transport: ActorTransport { get }+  +  static func resolve<Identity>(id identity: Identity, using transport: ActorTransport) +    throws -> Self?+    where Identity: ActorIdentity++}+```++We refer to the creation of a remote instance as "resolving" a remote actor, because it does *not* create an instance of the distributed actor if one does not exist. The actor's identity must be valid when calling `resolve` or resolution will fail:++```swift+extension Capybara {+  distributed func meet(friend: ActorIdentity) { ... }+}++func example3(transport: ActorTransport, friend name: ActorIdentity) async throws {+  // make local instance+  let a = Capybara(using: transport)++  // resolve some other instance+  let b = try Capybara.resolve(name, using: transport)+  +  // identities are Codable+  try await a.meet(b.id)++  // resolve a local instance from an ID+  let aliasForA = try Capybara.resolve(a.id, using: transport)+}+```++The examples so far have assumed that the actor's identity has already been obtained from *somewhere*. But, for a truly remote instance located in a different process, how does the current process obtain that actor's identity, in order to resolve it? ++This task of obtaining or exchanging actor identities is left to specific transport libraries, because it falls under the general problem of service discovery, whose solutions differ based on usage environments. In server environments, it is typical to have a single, fixed location where identity lookups may be performed. For example, those locations can be key-value databases, DNS lookups, or any other more specialized technique. In Swift's existing server ecosystem, the [swift-service-discovery](https://github.com/apple/swift-service-discovery) library can be used to exchange actor identities with a number of service discovery implementations. In the future, it is expected that additional actor-specialized libraries will become available.++### Distributed Actor Example++Now that we know distributed actor basics, we can rewrite the RPC example from earlier using distributed actors. We do so by changing `actor Counter`  to `distributed actor Counter`, and marking the `increment` and `get` methods as `distributed` too. This is all we need to do to prepare this actor for distributed calls.++```swift+import _Distributed++distributed actor Counter { +  var count: Int = 0+  +  distributed func increment() {+    count += 1+  }+  +  distributed func get() -> Int { +    return count+  }+}+```++For the RPC example, some process must first locally create an instance of the distributed actor `Counter`. This is done by calling`Counter.init(transport:)` and passing the specific transport we want it to accessed through:++```swift+import FishyTransport ++let transport = FishyTransport(...)+let counter = Counter(transport: transport)++/* Optional: register counter.id in some service discovery mechanism */+// discovery.announce("Counters", counter.id)+```+++Once we have either obtained the specific identity on a remote node, we can resolve it to get a reference to the remote actor, like this:++```swift+let id: AnyActorIdentity = try FishyTransport(host: "example.com", port: 1337, logLevel: nil)+  // Optional: instead of a fixed identity, get dynamic identity via service discovery, e.g.:+  //      await discovery.getAny("Counter", AnyActorIdentity.self)++let remoteCounter = try Counter.resolve(id, using: transport)+try await remoteCounter.increment() // remote call+```++#### Sample Transport / Application++In later sections, the details of implementing a transport are discussed. But, a core idea of distributed actors is to provide a clean separation between what the distributed object's API boundaries are, and how the inter-process communication is achieved. Thus, this proposal comes with a concrete sample application, along with a naive transport implementation, to demonstrate this separation: https://github.com/apple/swift-sample-distributed-actors-transport++The sample application forms a group of "nodes", each represented by an `ActorTransport` instance, and creates a number of `Chatter` distributed actors, all of which join a `ChatRoom` on a remote node. The chatters all communicate with the chat room and between eachother without having to know the transport details.++> **CAVEAT:** This sample project is very rough around the edges and is intended *only* to illustrate the ability to make use of the concepts introduced in this proposal. Please do not focus on the actual implementation details of the transport itself, but rather the interaction between it and the language proposal.++## Detailed design++For clarity, a number of details about this proposal were omitted from the Proposed Solution section. But, this section includes those details. Unless otherwise specified in this proposal, the semantics of a distributed actor are the same as a regular actor, as described in [SE-306](https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md).++### Location Transparency++The design of distributed actors intentionally does not provide facilities to easily determine whether an instance is local or remote. The programmer should not _need_ to think about where the instance is located, because Swift will make it work in either case. There are numerous benefits to embracing location transparency:++- The programmer can write a complex distributed systems algorithm and test it locally. Running that program on a cluster becomes merely a configuration and deployment change, without any additional source code changes.+- Distributed actors to be used with multiple transports without changing the actor's implementation.+- Actor instances can be balanced between nodes once capacity of a cluster changes, or be passivated when not in use, etc. There are many more advanced patterns for allocating instances, such as the "virtual actor" style as popularized by Orleans or Akka's cluster sharding.++One of the key restrictions that enable location transparency is the requirement that we pass arguments into the distributed actor that conform to `Codable`, so that they *can* be sent to another process if needed. But, there are some situations where the programmer _knows_ a particular instance is local to the process, so this restriction becomes bothersome.++#### Testing and breaking through Location Transparency++Programs based on distributed actors should always be written to respect location transparency, but sometimes it is useful to break through that abstraction. The most common situation where breaking through location transparency can be useful is when writing unit tests. Such tests may need to inspect state, or call non-distributed methods, of a distributed actor instance that is known to be local.++To support this kind of niche circumstance, all distributed actors offer a `withLocalDistributedActor` method, which executes a provided closure based on whether it is a local instance:++```swift+/// Runs the 'body' closure if and only if the passed 'actor' is a local instance.+/// +/// Returns `nil` if the actor was remote.+@discardableResult+func withLocalDistributedActor<Act, T>(+  _ actor: Act,+  _ body: (isolated Act) async throws -> T+) async rethrows -> T? where Act: DistributedActor+  +/// Runs the 'body' closure if and only if the passed 'actor' is a local instance.+/// +/// Invokes the 'else' closure if the actor instance was remote.+func withLocalDistributedActor<Act, T>(+  _ actor: Act+  _ body: (isolated Act) async throws -> T,+  else whenRemote (Act) async throws -> T+) async rethrows -> T where Act: DistributedActor+```++When the instance is local, the `withLocalDistributedActor` method exposes the distributed actor instance to the provided closure, as if it were a regular actor instance. This means you can invoke non-distributed methods when the actor instance is local, without relying on hacks that would trigger a crash if invoked on a remote instance. ++Except for the `withLocalDistributedActor` method, it is not possible to pass a closure to a distributed actor, since closures are not `Codable`. But, relying on `withLocalDistributedActor` outside of niche circumstances like testing is considered bad practice.+++### Typechecking Distributed Actors++This section discusses the semantic checking and restrictions placed on distributed actor types.++#### Protocol Conformances++A distributed actor is the only type that can conform to the `DistributedActor` protocol, and all distributed actors conform to that protocol. This protocol's list of requirements are defined earlier in this proposal, under the [Initialization section](#initialization). ++Only `distributed` or `nonisolated` methods can be specified in a distributed actor protocol `P`, which is any protocol inheriting from `DistributedActor`. This follows under the same reasoning as why `private` members cannot be stated as requirements in a protocol: the member is not be accessible from outside of the type's implementation, so it is not part of the type's interface.++> **NOTE:** One exception to this analogy with `private` is the escape-hatch method `withLocalDistributedActor`, which can be used from outside of the distributed actor to strip away the location transparency. Then, an isolated method required by the protocol becomes exposed. But, this situation appears to be too niche to be worth supporting.++Importantly, a distributed actor _cannot_ conform to the Actor protocol. For example, such a conformance would fail to provide correct location transparency:++```swift+extension Actor {+  func f() -> NonCodableButSendableType { ... }+}++func g(mda: MyDistributedActor) async {+  let a: Actor = mda as Actor // ❌ error: must be disallowed because...+  let result = await a.f()    // we cannot recieve the result of `f` if remote!+}+```++Conversely, regular actors also cannot conform to the `DistributedActor` protocol because of the same principle. See the Alternatives Considered section for an `AnyActor` protocol that was under consideration, but excluded from this proposal.++> A simple future addition to this proposal would be an `AnyActor` marker protocol, to tie the Actor and DistributedActor into a common type hierarchy. We discuss this idea in [Future Directions](#future-directions).++Distributed actors *may* conform to protocols if it is possible conform to such protocol using distributed functions. For example, a protocol with only async and throwing requirements, is possible to be conformed to by a distributed actor, however only if all of the functon parameters and return types also respect the additional checks a distributed actor has:++```swift+protocol GreeterProtocol { +  func greet(name: String) async throws // ok+}++distributed actor Greeter: GreeterProtocol { +  distributed func greet(name: String) async throws { ... } // ok (String is codable, async + throws)+}+```++It is possible to declare "distributed actor protocols", which are protocols which also require the `DistributedActor` protocol. Such protocols can only be conformed to by distributed actors, and can only contain `nonisolated` or `distributed` functions, and effectively can be thought of as form of "service API definitions", where the service is such distributed actor:++```swift+protocol DistributedGreeter: DistributedActor {+  distributed func greet(name: String)+}+```++As usual with distributed method declarations, the `async` and `throws` effects are implicit, yet may be specified explicitly as well.++##### Default Conformances++The `Equatable` and `Hashable` protocols from the Swift standard library require conformers to provide a way to distinguish between equivalent instances. When value types like structs and enums are declared to conform to these protocols, the `==` and `hash` witnesses are automatically synthesized, if the programmer does not specify them.++For distributed actors, any realistic `ActorTransport` will require conformance to `Equatable` and `Hashable`. While distributed actors are reference types, they are designed to have a stable, sharable identifier associated with each instance. Thus, Swift will automatically require that distributed actors conform to these protocols, and provide the following witnesses:++```swift+extension DistributedActor: Equatable {+  public static func ==(lhs: Self, rhs: Self) -> Bool {+    lhs.id == rhs.id+  }+}++extension DistributedActor: Hashable { +  nonisolated public func hash(into hasher: inout Hasher) {+    self.id.hash(into: &hasher)+  }+}+```++It is difficult to imagine any other witnesses for these protocols, because any accesses to the actor's internal state would require an asynchronous operation.++#### Distributed Methods++Distributed methods are declared by writing the `distributed` keyword in the place of a declaration modifier, under the `actor-isolation-modifier` production rule as specified by [the grammar in TSPL](https://docs.swift.org/swift-book/ReferenceManual/Declarations.html#grammar_declaration-modifiers). Only methods can use `distributed` as a declaration modifier, and no order is specified this modifier.+A `distributed actor` type, extensions of such a type, and `DistributedActor` inherting protocols are the only places where distributed method declarations are allowed. This is because, in order to implement a distributed method, a transport and identity must be associated with the values carrying the method. Distributed methods can synchronously refer to any of the state isolated to the distributed actor instance.++As a consequence of the request-response nature of distributed methods, `inout` parameters are not supported. While subscripts are similar to methods, they are not allowed for a distributed actor. A subscript's usefulness is strongly limited by both their lack of support for being `distributed` (e.g., could only support read-only subscripts, because no coroutine-style accessors) and their lightweight syntax can lead to the same problems as properties.+++### Distributed Actor lifecycle++The lifecycle of a distributed actor is important to consider, because it is a key part of how location transparency is achieved.++#### Local Initialization++All user-defined designated initializers of a distributed actor can be collectively referred to as "local" initializers. All local initializers of a distributed actor must accept exactly one `ActorTransport` parameter. This `ActorTransport` parameter is implicitly used to fulfil an important contract with the transport and the actor instance itself. Namely, all distributed actor instances must be assigned an `ActorIdentity`, which is provided by the transport. In addition, once fully initialized, the actor must inform the transport that it is ready to receive messages. Conceptually, these steps can be described as:++```swift+distributed actor DA {+  init(..., transport: ActorTransport) {+    // Step 1: Try to set-up the transport and identity.+    self._transport = transport+    self._id = AnyActorIdentity(transport.assignIdentity(Self.self))+    +    // Step 2: User code is executed, which may fail, access the identity, etc.+    +    // Step 3: The initializer did not fail; notify the transport.+    transport.actorReady(self)+  }+}+```++If no user-defined designated initializer is provided, a default initializer is synthesized that requires a named parameter `transport` of type `ActorTransport`, i.e., `init(transport:)`. The access level of the synthesized init is the same as the distributed actor declaration (i.e. for an internal distributed actor, the init is internal, and public for a public one). As with other nominal types, this initializer is only synthesized by the compiler if all stored properties can be default-initialized.++```swift+distributed actor DA {+  // synthesized: init(transport: ActorTransport) { ... }+}+```++#### Remote Resolution++There is no representation of a remote initializer for a distributed actor, because a remote instance has *conceptually* already been initialized. Thanks to location transparency, a remote instance is not even guaranteed to exist until it is actually needed. This is an important implementation detail that enables efficient implementations of distributed systems. To make this more concrete, consider a simplified version of the static `resolve` function that is synthesized for all distribtued actors:
There is no representation of a remote initializer for a distributed actor, because a remote instance has *conceptually* already been initialized. Thanks to location transparency, a remote instance is not even guaranteed to exist until it is actually needed. This is an important implementation detail that enables efficient implementations of distributed systems. To make this more concrete, consider a simplified version of the static `resolve` function that is synthesized for all distributed actors:
ktoso

comment created time in 20 hours

PullRequestReviewEvent

Pull request review commentapple/swift-evolution

[Pitch] Distributed Actors

+# Distributed Actors++* Proposal: [SE-NNNN](NNNN-distributed-actors.md)+* Authors: [Konrad 'ktoso' Malawski](https://github.com/ktoso), [Dario Rexin](https://github.com/drexin), [Doug Gregor](https://github.com/DougGregor), [Tomer Doron](https://github.com/tomerd), [Kavon Farvardin](https://github.com/kavon)+* Review Manager: TBD+* Status: **Implementation in progress**+* Implementation: +  * Partially available in [recent `main` toolchain snapshots](https://swift.org/download/#snapshots) behind the `-enable-experimental-distributed` feature flag. +  * This flag also implicitly enables `-enable-experimental-concurrency`.+* Sample app:+  * A sample app, showcasing how the various "pieces" work together is available here:+    [https://github.com/apple/swift-sample-distributed-actors-transport](https://github.com/apple/swift-sample-distributed-actors-transport)++## Table of Contents+++<!-- @import "[TOC]" {cmd="toc" depthFrom=1 depthTo=6 orderedList=false} -->++<!-- code_chunk_output -->++- [Distributed Actors](#distributed-actors)+  - [Table of Contents](#table-of-contents)+  - [Introduction](#introduction)+  - [Motivation](#motivation)+  - [Proposed solution](#proposed-solution)+    - [Distributed actors](#distributed-actors-1)+      - [Properties](#properties)+      - [Methods](#methods)+      - [Initialization](#initialization)+    - [RPC Example](#rpc-example)+  - [Detailed design](#detailed-design)+    - [Location Transparency](#location-transparency)+    - [Typechecking Distributed Actors](#typechecking-distributed-actors)+      - [Protocol Conformances](#protocol-conformances)+        - [Default Conformances](#default-conformances)+      - [Distributed Methods](#distributed-methods)+    - [Distributed Actor lifecycle](#distributed-actor-lifecycle)+      - [Local Initialization](#local-initialization)+      - [Remote Resolution](#remote-resolution)+      - [Deinitialization](#deinitialization)+    - [Actor Transports](#actor-transports)+      - [Actor Identity](#actor-identity)+      - [Transporting Errors](#transporting-errors)+    - [Sharing and Discovery](#sharing-and-discovery)+      - [Distributed Actors are `Codable`](#distributed-actors-are-codable)+      - [Discovering Existing Instances](#discovering-existing-instances)+  - [Alternatives Considered](#alternatives-considered)+    - [Creating only a library and/or tool](#creating-only-a-library-andor-tool)+    - [Special Actor spawning APIs](#special-actor-spawning-apis)+      - [Explicit `spawn(transport)` keyword-based API](#explicit-spawntransport-keyword-based-api)+      - [Global eagerly initialized transport](#global-eagerly-initialized-transport)+      - [Directly adopt Akka-style Actors References `ActorRef<Message>`](#directly-adopt-akka-style-actors-references-actorrefmessage)+  - [Acknowledgments & Prior Art](#acknowledgments-prior-art)+  - [Source compatibility](#source-compatibility)+  - [Effect on ABI stability](#effect-on-abi-stability)+  - [Effect on API resilience](#effect-on-api-resilience)+  - [Changelog](#changelog)+- [Appendix](#appendix)+  - [Runtime implementation details](#runtime-implementation-details)+      - [Remote `distributed actor` instance allocation](#remote-distributed-actor-instance-allocation)+      - [`distributed func` internals](#distributed-func-internals)+  - [Future Directions](#future-directions)+      - [The `AnyActor` marker protocol](#the-anyactor-marker-protocol)+    - [Storing and requiring more specific Transport types](#storing-and-requiring-more-specific-transport-types)+    - [Ability to customize parameter/return type requirements](#ability-to-customize-parameterreturn-type-requirements)+    - [Resolving DistributedActor bound protocols](#resolving-distributedactor-bound-protocols)+    - [Synthesis of `_remote` and well-defined `Envelope<Message>` functions](#synthesis-of-_remote-and-well-defined-envelopemessage-functions)+    - [Support for `AsyncSequence`](#support-for-asyncsequence)+    - [Ability to hardcode actors to specific shared transport](#ability-to-hardcode-actors-to-specific-shared-transport)+    - [Actor Supervision](#actor-supervision)+  - [Related Work](#related-work)+    - [Swift Distributed Tracing integration](#swift-distributed-tracing-integration)+    - [Distributed `Deadline` propagation](#distributed-deadline-propagation)+    - [Potential Transport Candidates](#potential-transport-candidates)+  - [Background](#background)++<!-- /code_chunk_output -->++++## Introduction++Swift's [actors](https://github.com/apple/swift-evolution/blob/main/proposals/0306-actors.md) are a relatively new building block for creating concurrent programs. The mutable state isolated by actors can only be interacted with by one task at any given time, eliminating a whole class of data races. But, the general [actor model](https://en.wikipedia.org/wiki/Actor_model) works equally well for distributed systems too.++This proposal introduces *distributed actors*, which are an extension of regular Swift actors that allows developers to take full advantage of the general actor model of computation. Distributed actors allow developers to scale their actor-based programs beyond a single process or node, without having to learn many new concepts.++Unlike regular actors, distributed actors take advantage of *[location transparency](https://en.wikipedia.org/wiki/Location_transparency)*, which relies on a robust, sharable handle (or name) for each actor instance. These handles can reference actor instances located in a different program process, which may even be running on a different machine that is accessible over a network. After resolving an actor handle to a specific instance located _somewhere_, the basic interactions with the actor instance closely mirrors that of regular actors. Distributed actors abstract over the communication mechanism that enables location transparency, so that the implementation can be extended to different domains. ++## Motivation++Distributed systems are not just for high-performance computing. Many of the programs written today are part of a distributed system. If your program uses *any* kind of [inter-process communication](https://en.wikipedia.org/wiki/Inter-process_communication) (IPC), i.e., it communicates with entities outside of its process on the same or a different machine, then it can be viewed as a distributed system. Real-world examples include networked applications, such as games, chat or media applications.++Distributed systems are pervasive, yet they require significant effort to implement in Swift. Consider this simple example of code that establishes a connection with a server and listens for messages that trigger some action:++```swift+import Foundation++actor Counter {+  var count: Int = 0+  func increment() {+    count += 1+  }+  func get() -> Int { return count }+}++class ConnectionManager {+  var connection: URLSessionWebSocketTask+  var state: Counter++  // Initialize a connection with the other server+  // and begin listening for messages.+  init?(_ serverURL: URLRequest, sharing counter: Counter) {+    state = counter+    connection = URLSession.shared.webSocketTask(with: serverURL)+    guard connection.error != nil else {+      return nil+    }+    listenLoop()+  }++  private func listenLoop() {+    Task.detached {+      switch try? await self.connection.receive() {+        case .some(let message):+          // NOTE: skip message deserialization; assume 1 message kind!+          await self.state.increment()  // perform action on local instance+          self.listenLoop() // listen for the next connection.+        default:+          return // end listen loop+      }+    }+  }++  deinit {+    connection.cancel(with: .normalClosure, reason: nil)+  }+}++let counter = Counter() // some shared actor instance++// Start the connection!+let serverReq = URLRequest(url: URL(string: "http://example.com:1337")!)+let c = ConnectionManager(serverReq, sharing: counter)+```++This is a barebones implementation of a unidirectional remote-procedure call (RPC), where the code above corresponds to the receiver's side that implements the requests, but does not perform responses. In this example, notice the reliance on a regular actor to synchronize accesses to its state, because the state can be accessed on the reciever's side too. While the sender-side code is omitted, it is already apparent that the basic pieces of an RPC-based distributed program involves:++1. Resolving / maintaining a connection.+2. Serializing and deserializing messages.+3. Servicing requests.++ The distributed actors in this proposal are designed to model an actor whose instance may be located in another program process. This means that the sender side can interact with the `Counter`'s state without the extra boilerplate. Specifically, distributed actors abstract over the actor's identifier and the communication transport, such as TCP/UDP, WebSocket or cross-process transports like XPC. This abstraction allows distributed code to focus on the implementation of the distributed actor's operations, using the familiar syntax and semantics of Swift's regular actors, and without needing to implement the communication in an ad-hoc manner. The same definition of a distributed actor is compatible with various identity and transport implementations, instances of which can even co-exist simultaneously in the same process.++## Proposed solution++There are several new concepts introduced by this proposal, which will be described in this section.++### Distributed actors++Distributed actors are a special flavor of the actor type that enforces additional rules on the type and its values, in order to achieve location transparency. They are written by prepending `distributed` to an actor declaration, like so:++```swift+distributed actor Player {+  let name: String+  var score: Int+  var teammates: [Player]+}+```++Distributed actors adopt the same isolation rules of regular actors, but because any instance may actually refer to an actor in another process (i.e., location transparency), extra rules are applied.++#### Properties+Developers should think carefully about operations that cross into the actor's isolation domain, because the cost of each operation can be expensive (e.g., if the actor is on a machine across the internet). Properties make it very easy to accidentially make multiple round-trips:++```swift+func example1(p: Player) async throws -> (String, Int) {+  try await (p.name, p.score) // ❌ might make two slow round-trips to `p`+}+```++Instead, methods are strongly encourged so that accesses can be batched.+Thus, access to a distributed actor's properties (stored and computed) from outside of the actor's isolation are forbidden. In addition, properties cannot be `nonisolated` or participate in a key-path.++#### Methods++Regular methods isolated to the distributed actor are not accessible from outside of the actor's isolation context. A new kind of method declaration, called a *distributed method*, are the only kind of isolated members that can be accessed from outside of a distributed actor. Nonisolated methods can be defined as usual, but a distributed method cannot be marked `nonisolated`. A distributed method is defined within a distributed actor by writing `distributed` in front of the method's declaration:++```swift+extension Player {+  distributed func addTeammate(p: Player) {+    guard !haveTeammate(p) else {+      return+    }+    teammates.append(p)+  }++  func haveTeammate(p: Player) -> Bool {+    return teammates.contains(p)+  }+}++func example2(a: Player, b: Player) async throws {+  a.haveTeammate(b) // ❌ error, function not `distributed`+  +  try await a.addTeammate(b)  // ✅ OK. distributed actors are Codable!+}+```++In addition to conforming to `Sendable`, a distributed function's parameters and its return type are all required to conform to the [`Codable` protocol](https://developer.apple.com/documentation/swift/codable). A codable value supports serialization and deserialization, which is nessecary in order to maintain location transparency. For example, servicing a method call on a distributed actor instance can involve sending the arguments to another process and awaiting a response.++Like a regular actor method, an expression that calls a distributed function is  treated as `async` from outside of the actor's isoalation. But for a distributed actor, such calls are _also_ treated as `throws` when outside of the actor's isolation, because a request to call a distributed method is not guaranteed to recieve a response. The underlying process that hosts the distributed actor instance may be on another machine that crashed, or a connection may be lost, etc. To help make this clear, consider the following example, which contains only the necessary `try` and `await` expressions:++```swift+distributed actor Greeter {+  distributed func englishGreeting() -> String { "Hello!" }+  distributed func japaneseGreeting() throws -> String { "こんにちは!" }+  distributed func germanGreeting() async -> String { "Servus!" }+  distributed func polishGreeting() async throws -> String { "Cześć!" }+  +  func inside() async throws { +    _ = self.englishGreeting()+    _ = try self.japaneseGreeting()+    _ = await self.germanGreeting()+    _ = try await self.polishGreeting()+  }+} // end of Greeter++func outside(greeter: Greeter) async throws { +  _ = try await greeter.englishGreeting()+  _ = try await greeter.japaneseGreeting()+  _ = try await greeter.germanGreeting()+  _ = try await greeter.polishGreeting()+}+```++Errors thrown by the underlying transport due to connection or messaging problems must conform to the `ActorTransportError` protocol. A distributed function can also be explicitly marked as `throws`, as shown above, but the underlying transport is responsible for determining how to forward any thrown errors, since errors thrown by a distributed function do _not_ have to be `Codable`.++One benefit of explicitly marking distributed functions with the `distributed` keyword that it makes clear to the programmer (and tools, such as IDEs) where networking costs are to be expected when using a distributed actor. This is an improvement over today's world, where any function might ultimately perform network calls, and we are not able to easily notice them, for example, in the middle of a tight loop.++#### Initialization+Distributed actors and functions are largely a set of a rules that enforce location transparency. In order for distributed actors to actually perform any remote communication, it is necessary to initialize them with an actual implementation of an `ActorTransport`, which is a protocol that is implementable by library writers. A *transport* handles all of the connection details that underpin distributed actors. An actor transport can take advantage of any networking, cross-process, or even in-memory approach to communicate between instances, as long as it conforms to the protocol correctly.++There are two ways to initialize a distributed actor, but in any case, a distributed actor is always associated with some `ActorTransport`. To create a new **local** instance of a distributed actor, call the actor's `init` as usual. All non-delegating initializers, which fully-initialize the distributed actor, are required to accept exactly one argument that conforms to `ActorTransport`:++```swift+distributed actor Capybara { +  var name: String+  +  // ✅ ok. exactly one transport param for non-delegating initializer.+  init(named name: String, using: ActorTransport) {+    self.name = name+  }++  // ✅ ok. no transport param, but it's a delegating initializer.+  convenience init() {+    self.init(named: "Happybara", using: Defaults.IPoAC)+  }++  // ❌ error, too many transport params for a non-delegating init.+  init(main: ActorTransport, alt: ActorTransport) {+    self.name = "Sleepybara"+  }+  +  // ❌ error, no transports for a non-delegating init.+  init(withName name: String) {+    self.name = name+  }+}+```++Swift will automatically associate a `Capybara` instance with the transport passed to `init(named:using:)`, without the programmer explicitly using the transport argument. This is done, instead of requiring a specific `let`-bound property to be initialized, because the `ActorTransport` is specially stored in the instance, depending on whether it represents a remote or local instance of the actor.++A **remote** instance can be only be resolved if the programmer also has the instance's `ActorIdentity`. An `ActorIdentity` is a protocol that specifies the minimum capabilities of a durable, unique identifier associated with a distributed actor instance. All distributed actors have two computed properties that are accessible from anywhere, and a static `resolve` function:++```swift+public protocol DistributedActor: Sendable, Codable, Identifiable, Hashable {++  /// 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.+  ///+  /// Conformance to this requirement is synthesized automatically for any+  /// `distributed actor` declaration.+  /*special*/ var id: AnyActorIdentity { get }++  /// Transport responsible for managing the lifecycle and messaging aspects of this actor.+  /*special*/ var transport: ActorTransport { get }+  +  static func resolve<Identity>(id identity: Identity, using transport: ActorTransport) +    throws -> Self?+    where Identity: ActorIdentity++}+```++We refer to the creation of a remote instance as "resolving" a remote actor, because it does *not* create an instance of the distributed actor if one does not exist. The actor's identity must be valid when calling `resolve` or resolution will fail:++```swift+extension Capybara {+  distributed func meet(friend: ActorIdentity) { ... }+}++func example3(transport: ActorTransport, friend name: ActorIdentity) async throws {+  // make local instance+  let a = Capybara(using: transport)++  // resolve some other instance+  let b = try Capybara.resolve(name, using: transport)+  +  // identities are Codable+  try await a.meet(b.id)++  // resolve a local instance from an ID+  let aliasForA = try Capybara.resolve(a.id, using: transport)+}+```++The examples so far have assumed that the actor's identity has already been obtained from *somewhere*. But, for a truly remote instance located in a different process, how does the current process obtain that actor's identity, in order to resolve it? ++This task of obtaining or exchanging actor identities is left to specific transport libraries, because it falls under the general problem of service discovery, whose solutions differ based on usage environments. In server environments, it is typical to have a single, fixed location where identity lookups may be performed. For example, those locations can be key-value databases, DNS lookups, or any other more specialized technique. In Swift's existing server ecosystem, the [swift-service-discovery](https://github.com/apple/swift-service-discovery) library can be used to exchange actor identities with a number of service discovery implementations. In the future, it is expected that additional actor-specialized libraries will become available.++### Distributed Actor Example++Now that we know distributed actor basics, we can rewrite the RPC example from earlier using distributed actors. We do so by changing `actor Counter`  to `distributed actor Counter`, and marking the `increment` and `get` methods as `distributed` too. This is all we need to do to prepare this actor for distributed calls.++```swift+import _Distributed++distributed actor Counter { +  var count: Int = 0+  +  distributed func increment() {+    count += 1+  }+  +  distributed func get() -> Int { +    return count+  }+}+```++For the RPC example, some process must first locally create an instance of the distributed actor `Counter`. This is done by calling`Counter.init(transport:)` and passing the specific transport we want it to accessed through:++```swift+import FishyTransport ++let transport = FishyTransport(...)+let counter = Counter(transport: transport)++/* Optional: register counter.id in some service discovery mechanism */+// discovery.announce("Counters", counter.id)+```+++Once we have either obtained the specific identity on a remote node, we can resolve it to get a reference to the remote actor, like this:++```swift+let id: AnyActorIdentity = try FishyTransport(host: "example.com", port: 1337, logLevel: nil)

Oh thanks, somethign went wrong here, this should be just "imagine we got an identity"

let id: AnyActorIdentity = ...
ktoso

comment created time in 20 hours

PullRequestReviewEvent

Pull request review commentswift-server/guides

Swift Concurrency adoption guidelines for Swift Server Libraries

+# Swift Concurrency adoption guidelines for Swift Server Libraries++This writeup attempts to provide a set of guidelines to follow by authors of server-side Swift libraries. Specifically a lot of the discussion here revolves around what to do about existing APIs and libraries making extensive use of Swift NIO’s `EventLoopFuture` and related types.++Swift Concurrency is a multi-year effort. It is very valuable for the server community to participate in this multi-year adoption of the concurrency features, one by one, and provide feedback while doing so. As such, we should not hold off adopting concurrency features until Swift 6 as we may miss valuable opportunity to improve the concurrency model.++In 2021 we saw structured concurrency and actors arrive with Swift 5.5. Now is a great time to provide APIs using those primitives. In the future we will see fully checked Swift concurrency. This will come with breaking changes. For this reason adopting the new concurrency features can be split into two phases.+++## What you can do right now++### API Design++Firstly, existing libraries should strive to add `async` functions where possible to their user-facing “surface” APIs in addition to existing `*Future` based APIs wherever possible. These additive APIs can be gated on the Swift version and can be added without breaking existing users' code, for example like this:++```swift+extension Worker {+  func work() -> EventLoopFuture<Value> { ... }+  +  #if swift(>=5.5)+  func work() async throws -> Value { ... }+  #endif+}+```++If a function cannot fail but was using futures before, it should not include the `throws` keyword in its new incarnation. ++Such adoption can begin immediately, and should not cause any issues to existing users of existing libraries. ++### SwiftNIO helper functions++To allow an easy transition to async code, SwiftNIO offers a number of helper methods on `EventLoopFuture` and `-Promise`. Those live in the `_NIOConcurrency` module and will move to `NIOCore` once Swift concurrency is released.++On every `EventLoopFuture` you can call `.get()` to transition the future into an `await`-able invocation. If you want to translate async/await calls to an `EventLoopFuture` we recommend the following pattern:++```swift +#if swift(>=5.5)+func yourAsyncFunctionConvertedToAFuture(on eventLoop: EventLoop) +    -> EventLoopFuture<Result> {+    let promise = context.eventLoop.makePromise(of: Out.self)+    promise.completeWithTask {+        try await yourMethod(yourInputs)+    }+    return promise.futureResult+}+#endif+```++Further helpers exist for `EventLoopGroup`, `Channel`, `ChannelOutboundInvoker` and `ChannelPipeline`.++### Sendable Checking++> [SE-0302][SE-0302] introduced the `Sendable` protocol, which is used to indicate which types have values that can safely be copied across actors or, more generally, into any context where a copy of the value might be used concurrently with the original. Applied uniformly to all Swift code, `Sendable` checking eliminates a large class of data races caused by shared mutable state.+>+> -- from [Staging in Sendable checking][sendable-staging], which outlines the `Sendable` adoption plan for Swift 6.++In the future we will see fully checked Swift concurrency. The language features to support this are the `Sendable` protocol and the `@Sendable` keyword for closures. Since sendable checking will break existing Swift code, a new major Swift version is required.++To ease the transition to fully checked Swift code, it is possible to annotate your APIs with the `Sendable` protocol today.++You can start adopting Sendable and getting appropriate warnings in Swift 5.5 already by passing the `-warn-concurrency` flag, you can do so in SwiftPM for the entire project like so:++```+swift build -Xswiftc -Xfrontend -Xswiftc -warn-concurrency+```+++#### Sendable checking today++Sendable checking is currently disabled in Swift 5.5(.0) because it was causing a number of tricky situations for which we lacked the tools to resolve.++Most of these issues have been resolved on today’s `main` branch of the compiler, and are expected to land in the next Swift 5.5 releases. It may be worthwhile waiting for adoption until the next version(s) after 5.5.0.++For example, one of such capabilities is the ability for tuples of `Sendable` types to conform to `Sendable` as well. We recommend holding off adoption of `Sendable` until this patch lands in Swift 5.5 (which should be relatively soon). With this change, the difference between Swift 5.5 with `-warn-concurrency` enabled and Swift 6 mode should be very small, and manageable on a case by case basis.++#### Backwards compatibility of declarations and “checked” Swift Concurrency++Adopting Swift Concurrency will progressively cause more warnings, and eventually compile time errors in Swift 6 when sendability checks are violated, marking potentially unsafe code.++It may be difficult for a library to maintain a version that is compatible with versions prior to Swift 6 while also fully embracing the new concurrency checks. For example, it may be necessary to mark generic types as `Sendable`, like so:++```swift+struct Container<Value: Sendable>: Sendable { ... }+```++Here, the `Value` type must be marked `Sendable` for Swift 6’s concurrency checks to work properly with such container. However, since the `Sendable` type does not exist in Swift prior to Swift 5.5, it would be difficult to maintain a library that supports both Swift 5.4+ as well as Swift 6.++In such situations, it may be helpful to utilize the following trick to be able to share the same `Container` declaration between both Swift versions of the library:++```swift+#if compiler(>=5.5)+public typealias MYPREFIX_Sendable = Swift.Sendable+#else +public typealias MYPREFIX_Sendable = Any+#endif+```++The `Any` alias is effectively a no-op when applied as generic constraint, and thus this way it is possible to keep the same `Container<Value>` declaration working across Swift versions.++### Task Local Values and Logging++The newly introduced Task Local Values API ([SE-0311][SE-0311]) allows for implicit carrying of metadata along with `Task` execution. It is a natural fit for for tracing and carrying metadata around with task execution, and e.g. including it in log messages. ++We are working on adjusting [SwiftLog](https://github.com/apple/swift-log) to become powerful enough to automatically pick up and log specific task local values. This change will be introduced in a source compatible way. ++For now libraries should continue using logger metadata, but we expect that in the future a lot of the cases where metadata is manually passed to each log statement can be replaced with setting task local values. ++### Preparing for the concept of Deadlines++Deadlines are another feature that closely relate to Swift Concurrency, and were originally pitched during the early versions of the Structured Concurrency proposal and later on moved out of it. The Swift team remains interested in introducing deadline concepts to the language and some preparation for it already has been performed inside the concurrency runtime. Right now however, there is no support for deadlines in Swift Concurrency and it is fine to continue using mechanisms like `NIODeadline` or similar mechanisms to cancel tasks after some period of time has passed. ++Once Swift Concurrency gains deadline support, they will manifest in being able to cancel a task (and its child tasks) once such deadline (point in time) has been exceeded. For APIs to be “ready for deadlines” they don’t have to do anything special other than preparing to be able to deal with `Task`s and their cancellation.++### Cooperatively handling Task cancellation++`Task` cancellation exists today in Swift Concurrency and is something that libraries may already handle. In practice it means that any asynchronous function (or function which is expected to be called from within `Task`s), may use the [`Task.isCancelled`](https://developer.apple.com/documentation/swift/task/3814832-iscancelled) or [`try Task.checkCancellation()`](https://developer.apple.com/documentation/swift/task/3814826-checkcancellation) APIs to check if the task it is executing in was cancelled, and if so, it may cooperatively abort any operation it was currently performing.++Cancellation can be useful in long running operations, or before kicking off some expensive operation. For example, an HTTP client MAY check for cancellation before it sends a request — it perhaps does not make sense to send a request if it is known the task awaiting on it does not care for the result anymore after all!++Cancellation in general can be understood as “the one waiting for the result of this task is not interested in it anymore”, and it usually is best to throw a “cancelled” error when the cancellation is encountered. However, in some situations returning a “partial” result may also be appropriate (e.g. if a task is collecting many results, it may return those it managed to collect until now, rather than returning none or ignoring the cancellation and collecting all remaining results).++## What to expect with Swift 6++### Sendable: Global variables & imported code++Today, Swift 5.5 does not yet handle global variables at all within its concurrency checking model. This will soon change but the exact semantics are not set in stone yet. In general, avoid using global properties and variables wherever possible to avoid running into issues in the future. Consider deprecating global variables if able to.++Some global variables have special properties, such as `errno` which contains the error code of system calls. It is a thread local variable and therefore safe to read from any thread/`Task`. We expect to improve the importer to annotate such globals with some kind of “known to be safe” annotation, such that the Swift code using it, even in fully checked concurrency mode won’t complain about it. Having that said, using `errno` and other “thread local” APIs is very error prone in Swift Concurrency because thread-hops may occur at any suspension point, so the following snippet is very likely incorrect:++```swift+sys_call(...)+await ...+let err = errno // BAD, we are most likely on a different thread here (!)+```++Please take care when interacting with any thread-local API from Swift Concurrency. If your library had used thread local storage before, you will want to move them to use [task-local values](https://github.com/apple/swift-evolution/blob/main/proposals/0311-task-locals.md) instead as they work correctly with Swift’s structured concurrency tasks.++Another tricky situation is with imported C code. There may be no good way to annotate the imported types as Sendable (or it would be too troublesome to do so by hand). Swift is likely to gain improved support for imported code and potentially allow ignoring some of the concurrency safety checks on imported code. ++These relaxed semantics for imported code are not implemented yet, but keep it in mind when working with C APIs from Swift and trying to adopt the `-warn-concurrency` mode today. Please file any issues you hit on [bugs.swift.org](https://bugs.swift.org/secure/Dashboard.jspa) so we can inform the development of these checking heuristics based on real issues you hit.++### Custom Executors++We expect that Swift Concurrency will allow custom executors in the future. A custom executor would allow the ability to run actors / tasks “on” such executor. It is possible that `EventLoop`s could become such executors, however the custom executor proposal has not been pitched yet.++While we expect potential performance gains from using custom executors “on the same event loop” by avoiding asynchronous hops between calls to different actors, their introduction will not fundamentally change how NIO libraries are structured.++The guidance here will evolve as Swift Evolution proposals for Custom Executors are proposed, but don’t hold off adopting Swift Concurrency until custom executors “land” - it is important to start adoption early. For most code we believe that the gains from adopting Swift Concurrency vastly outweigh the slight performance cost actor-hops might induce.+++### Reduce use of Swift NIO Futures as “Concurrency Library“++Swift NIO currently provides a number of currency types for the Swift on Server ecosystem. Most notably `EventLoopFuture`s and `EventLoopPromise`s, that are used widely for asynchronous results. While the SSWG recommended using those at the API level in the past for easier interplay of server libraries, we advise to deprecate or remove such APIs once Swift 6 lands. The swift-server ecosystem should go all in on the structured concurrency features the languages provides. For this reason, it is crucial to provide async/await APIs today, to give your library users time to adopt the new APIs.++Some NIO types will remain however in the public interfaces of Swift on server libraries. We expect that networking clients and servers continue to be initialized with `EventLoopGroup`s. The underlying transport mechanism (`NIOPosix` and `NIOTransportServices`) should become implementation details however and should not be exposed to library adopters.++### NIO 3++While subject to change, it is likely that Swift NIO will cut a 3.0 release in the months after Swift 6.0, at which point in time Swift will have enabled “full” `Sendable` checking.++Do not expect NIO to suddenly become “more async”, NIO’s inherent design principles are about performing small tasks on the event loop and using Futures for any async operations. The design of NIO is not expected to change. It is crucial to its high performance networking design. Channel pipelines are not expected to become “async”.++The NIO team will however use the chance to remove deprecated APIs and improve some APIs. The scope of changes should be comparable to the NIO1 → NIO2 version bump. If your SwiftNIO code compiles today without warnings, chances are high that it will continue to work without modifications in NIO3.++After the release of NIO3, NIO2 will see bug fixes only.++### End-user code breakage++It is expected that Swift 6 will break some code. As mentioned Swift NIO 3 is also going to be released sometime around Swift 6 dropping. Keeping this in mind, it might be a good idea to align major version releases around the same time, along with updating version requirements to Swift 6 and NIO 3 in your libraries.

That's a good point, a bit too "slang-y" I guess, thanks! I'll reword

fabianfett

comment created time in 21 hours

PullRequestReviewEvent

issue commentswift-server/swift-service-lifecycle

Fails to compile Xcode 13 RC (note: needs macOS release)

On linux everything is included in the toolchains yes.

joshuawright11

comment created time in a day

push eventfabianfett/guides

Konrad `ktoso` Malawski

commit sha 5a42e21ecd6b306d95200c59bb8c6d40cf77c98d

Apply suggestions from code review Co-authored-by: Kaitlin Mahar <kaitlinmahar@gmail.com>

view details

push time in a day

PullRequestReviewEvent

Pull request review commentswift-server/guides

Swift Concurrency adoption guidelines for Swift Server Libraries

+# Swift Concurrency adoption guidelines for Swift Server Libraries++This writeup attempts to provide a set of guidelines to follow by authors of server-side swift libraries. Specifically a lot of the discussion here revolves around what to do about existing APIs and libraries making extensive use of Swift NIO’s EventLoopFuture and related types.++Swift Concurrency is a multi-year effort. It is very valuable for the server community to participate in this multi-year adoption of the concurrency features, one by one, and provide feedback while doing so. As such, we should not hold off adopting concurrency features until Swift 6 as we may miss valuable opportunity to improve the concurrency model.++In 2021 we saw structured concurrency and actors. Now is a great time to provide APIs using those primitives. In the future we will see fully checked Swift concurrency. This will come with breaking changes. For this reason adopting the new concurrency features can be split into two phases.+++## What you can do right now++### API Design++Firstly, existing libraries should strive to add `async` functions where possible to their user-facing “surface” APIs in addition to existing `*Future` based APIs wherever possible. These additive APIs can be gated on the Swift version and can be added without breaking existing users, for example like this:++```swift+extension Worker {+  func work() -> EventLoopFuture<Value> { ... }+  +  #if swift(>=5.5)+  func work() async throws -> Value { ... }+  #endif+}+```++If a function cannot fail but was using futures before, it should not include the throws keyword in its new incarnation. ++Such adoption can begin immediately, and should not cause any issues to existing users of existing libraries. ++### SwiftNIO helper functions++To allow an easy transition to async code, SwiftNIO offers a number of helper methods on EventLoopFuture and -Promise. Those live in the `_NIOConcurrency` module and will move to NIOCore once Swift concurrency is released.++On every `EventLoopFuture` you can call `.get()` to transition the future into an awaitable invocation. If you want to translate async/await calls to an EventLoopFuture we recommend the following pattern:++```swift +#if swift(>=5.5)+func yourAsyncFunctionConvertedToAFuture(on eventLoop: EventLoop) +    -> EventLoopFuture<Result> {+    let promise = context.eventLoop.makePromise(of: Out.self)+    promise.completeWithTask {+        try await yourMethod(yourInputs)+    }+    return promise.futureResult+}+#endif+```++Further helpers exist for `EventLoopGroup`, `Channel`, `ChannelOutboundInvoker` and `ChannelPipeline`.++### Sendable Checking++> [SE-0302][SE-0302] introduced the Sendable protocol, which is used to indicate which types have values that can safely be copied across actors or, more generally, into any context where a copy of the value might be used concurrently with the original. Applied uniformly to all Swift code, Sendable checking eliminates a large class of data races caused by shared mutable state.+>+> -- from [Staging in Sendable checking][sendable-staging], which outlines the Sendable adoption plan for Swift 6.++In the future we will see fully checked Swift concurrency. The language features to support this are the `Sendable` protocol and the `@Sendable` keyword for closures. Since sendable checking will break existing Swift code, a new major Swift version is required.++To ease the transition to fully checked Swift code, it is possible to annotate your APIs with the Sendable protocol today.++You can start adopting Sendable and getting appropriate warnings in Swift 5.5 already by passing the `-warn-concurrency` flag, you can do so in SwiftPM for the entire project like so:++```+swift build -Xswiftc -Xfrontend -Xswiftc -warn-concurrency+```+++#### Sendable checking today++Sendable checking is currently disabled in Swift 5.5(.0) because it was causing a number of tricky situations for which we lacked the tools to resolve.++Most of these issues have been resolved on today’s `main` branch of the compiler, and are expected to land in the next Swift 5.5 releases. It may be worthwhile waiting with adoption until the next version(s) after 5.5.0.++For example, one of such capabilities is the ability for tuples of `Sendable` types to conform to `Sendable` as well. We recommend holding off adoption of `Sendable` until this patch lands in Swift 5.5 (which should be relatively soon). With this change, the difference between Swift 5.5 with `-warn-concurrency` enabled and Swift 6 mode should be very small, and manageable on a case by case basis.++#### Backwards compatibility of declarations and “checked” Swift Concurrency++Adopting Swift Concurrency will progressively cause more warnings, and eventually compile time errors in Swift 6 when sendability checks are violated, marking potentially unsafe code.++It may be difficult for a library to maintain a “pre Swift 6” version along with a Swift 6 version that fully embraces the new concurrency checks. For example, it may be necessary to mark generic types as Sendable, like so:++```swift+struct Container<Value: Sendable>: Sendable { ... }+```++Here, the `Value` type must be marked `Sendable` for Swift 6’s concurrency checks to work properly with such container. However, since the Sendable type does not exist in Swift prior to Swift 5.5, it would be difficult to maintain a library that supports both Swift 5.4+ as well as Swift 6.++In such situations, it may be helpful to utilize the following trick to be able to share the same Container declaration between both Swift versions of the library:++```swift+#if compiler(>=5.5)+public typealias MYPREFIX_Sendable = Swift.Sendable+#else +public typealias MYPREFIX_Sendable = Any+#endif+```++The Any alias is effectively a no-op when applied as generic constraint, and thus this way it is possible to keep the same Container<Value> declaration working across Swift versions.++### Task Local Values and Logging++The newly introduced TaskLocal Values API ([SE-0311][SE-0311]) allows for implicit carrying of metadata along with Task execution. It is a natural fit for for tracing and carrying metadata around with task execution, and e.g. including it in log messages. ++We are working on adjusting Swift Log to become powerful enough to automatically pick up and log specific task local values. This change will be introduced in a source compatible way. ++For now libraries should continue using logger metadata, but we expect that a lot of the manually passing metadata to each log statement can be replaced in the future with setting task local values. ++### Preparing for the concept of Deadlines++Deadlines are another feature that closely relate to Swift Concurrency, and were originally pitched during the early versions of the Structured Concurrency proposal and later on moved out of it. The Swift team remains interested in introducing deadline concepts to the language and some preparation for it already has been performed inside the concurrency runtime. Right now however, there is no support for deadlines in Swift Concurrency and it is fine to continue using mechanisms like NIODeadline or similar mechanisms to cancel tasks after some period of time has passed. ++Once Swift Concurrency gains deadline support, they will manifest in being able to cancel a task (and its child tasks) once such deadline (point in time) has been exceeded. For APIs to be “ready for deadlines” they don’t have to do anything special other than preparing to be able to deal with Tasks and their cancellation.++### Cooperatively handling Task cancellation++Task cancellation exists today in Swift Concurrency and is something that libraries may already handle. In practice it means that any asynchronous function (or function which is expected to be called from within Tasks), may use the Task.isCancelled or try Task.checkCancellation() APIs to check if the task it is executing in was cancelled, and if so, it may cooperatively abort any operation it was currently performing.++Cancellation can be useful in long running operations, or before kicking off some expensive operation. For example, an HTTP client MAY check for cancellation before it sends a request — it perhaps does not make sense to send a request if it is known the task awaiting on it does not care for the result anymore after all!++Cancellation in general can be understood as “the one waiting for the result of this task is not interested in it anymore”, and it usually is best to throw a “cancelled” error when the cancellation is encountered. However, in some situations returning a “partial” result may also be appropriate (e.g. if a task is collecting many results, it may return those it managed to collect until now, rather than returning none or ignoring the cancellation and collecting all remaining results).++## What to expect with Swift 6++### Sendable: Global variables & imported code++Today, Swift 5.5 does not yet handle global variables at all within its concurrency checking model. This will soon change but the exact semantics are not set in stone yet. In general, avoid using global properties and variables wherever possible to avoid running into issues in the future. Consider deprecating global variables if able to.++Some global variables have special properties, such as errno which contains the error code of system calls. It is a thread local variable and therefore safe to read from any thread/Task. We expect to improve the importer to annotate such globals with some kind of “known to be safe” annotation, such that the Swift code using it, even in fully checked concurrency mode won’t complain about it. Having that said, using errno and other “thread local” APIs is very error prone in Swift Concurrency because thread-hops may occur at any suspension point, so the following snippet is very likely incorrect:++```swift+sys_call(...)+await ...+let err = errno // BAD, we are most likely on a different thread here (!)+```++Please take care when interacting with any thread-local API from Swift Concurrency. If your library had used thread local storage before, you will want to move them to use task-local values (https://github.com/apple/swift-evolution/blob/main/proposals/0311-task-locals.md) instead as they work correctly with Swift’s structured concurrency tasks.++Another tricky situation is with imported C code. There may be no good way to annotate the imported types as Sendable (or it would be too troublesome to do so by hand). Swift is likely to gain improved support for imported code and potentially allow ignoring some of the concurrency safety checks on imported code. ++These relaxed semantics for imported code are not implemented yet, but keep it in mind when working with C APIs from Swift and trying to adopt the -warn-concurrency mode today. Please file any issues you hit so we can inform the development of these checking heuristics based on real issues you hit.

nice thanks

fabianfett

comment created time in a day

PullRequestReviewEvent

Pull request review commentswift-server/guides

Swift Concurrency adoption guidelines for Swift Server Libraries

+# Swift Concurrency adoption guidelines for Swift Server Libraries++This writeup attempts to provide a set of guidelines to follow by authors of server-side swift libraries. Specifically a lot of the discussion here revolves around what to do about existing APIs and libraries making extensive use of Swift NIO’s EventLoopFuture and related types.++Swift Concurrency is a multi-year effort. It is very valuable for the server community to participate in this multi-year adoption of the concurrency features, one by one, and provide feedback while doing so. As such, we should not hold off adopting concurrency features until Swift 6 as we may miss valuable opportunity to improve the concurrency model.++In 2021 we saw structured concurrency and actors. Now is a great time to provide APIs using those primitives. In the future we will see fully checked Swift concurrency. This will come with breaking changes. For this reason adopting the new concurrency features can be split into two phases.+++## What you can do right now++### API Design++Firstly, existing libraries should strive to add `async` functions where possible to their user-facing “surface” APIs in addition to existing `*Future` based APIs wherever possible. These additive APIs can be gated on the Swift version and can be added without breaking existing users, for example like this:++```swift+extension Worker {+  func work() -> EventLoopFuture<Value> { ... }+  +  #if swift(>=5.5)+  func work() async throws -> Value { ... }+  #endif+}+```++If a function cannot fail but was using futures before, it should not include the throws keyword in its new incarnation. ++Such adoption can begin immediately, and should not cause any issues to existing users of existing libraries. ++### SwiftNIO helper functions++To allow an easy transition to async code, SwiftNIO offers a number of helper methods on EventLoopFuture and -Promise. Those live in the `_NIOConcurrency` module and will move to NIOCore once Swift concurrency is released.++On every `EventLoopFuture` you can call `.get()` to transition the future into an awaitable invocation. If you want to translate async/await calls to an EventLoopFuture we recommend the following pattern:++```swift +#if swift(>=5.5)+func yourAsyncFunctionConvertedToAFuture(on eventLoop: EventLoop) +    -> EventLoopFuture<Result> {+    let promise = context.eventLoop.makePromise(of: Out.self)

Oh, thanks 👍

fabianfett

comment created time in a day

Pull request review commentswift-server/guides

Swift Concurrency adoption guidelines for Swift Server Libraries

+# Swift Concurrency adoption guidelines for Swift Server Libraries++This writeup attempts to provide a set of guidelines to follow by authors of server-side swift libraries. Specifically a lot of the discussion here revolves around what to do about existing APIs and libraries making extensive use of Swift NIO’s EventLoopFuture and related types.++Swift Concurrency is a multi-year effort. It is very valuable for the server community to participate in this multi-year adoption of the concurrency features, one by one, and provide feedback while doing so. As such, we should not hold off adopting concurrency features until Swift 6 as we may miss valuable opportunity to improve the concurrency model.++In 2021 we saw structured concurrency and actors. Now is a great time to provide APIs using those primitives. In the future we will see fully checked Swift concurrency. This will come with breaking changes. For this reason adopting the new concurrency features can be split into two phases.+++## What you can do right now++### API Design++Firstly, existing libraries should strive to add `async` functions where possible to their user-facing “surface” APIs in addition to existing `*Future` based APIs wherever possible. These additive APIs can be gated on the Swift version and can be added without breaking existing users, for example like this:

Hmm... I guess there MIGHT BE some library using the other future libraries but honestly I've not seen one for server focus that would use the other ones (PromiseKit etc), they're usually a bit iOS "tainted" hm...

fabianfett

comment created time in a day

PullRequestReviewEvent

pull request commentswift-server/guides

Swift Concurrency adoption guidelines for Swift Server Libraries

Thanks a lot for the edits @kmahar !

fabianfett

comment created time in a day

Pull request review commentswift-server/guides

Swift Concurrency adoption guidelines for Swift Server Libraries

+# Swift Concurrency adoption guidelines for Swift Server Libraries

Good point, will do

fabianfett

comment created time in a day

PullRequestReviewEvent