|
16 | 16 |
|
17 | 17 | ## Table of Contents
|
18 | 18 |
|
19 |
| -- [Custom Actor Executors](#custom-actor-executors) |
20 |
| - * [Introduction](#introduction) |
21 |
| - * [Motivation](#motivation) |
22 |
| - * [Proposed solution](#proposed-solution) |
23 |
| - * [Detailed design](#detailed-design) |
24 |
| - + [A low-level design](#a-low-level-design) |
25 |
| - + [Executors](#executors) |
26 |
| - + [Serial Executors](#serial-executors) |
27 |
| - + [ExecutorJobs](#executorjobs) |
28 |
| - + [Actors with custom SerialExecutors](#actors-with-custom-serialexecutors) |
29 |
| - + [Asserting on executors](#asserting-on-executors) |
30 |
| - + [Assuming actor executors](#asserting-actor-executors) |
31 |
| - + [Default Swift Runtime Executors](#default-swift-runtime-executors) |
32 |
| - * [Source compatibility](#source-compatibility) |
33 |
| - * [Effect on ABI stability](#effect-on-abi-stability) |
34 |
| - * [Effect on API resilience](#effect-on-api-resilience) |
35 |
| - * [Alternatives considered](#alternatives-considered) |
36 |
| - * [Future Directions](#future-directions) |
37 |
| - + [Overriding the MainActor Executor](#overriding-the-mainactor-executor) |
38 |
| - + [Executor Switching Optimizations](#executor-switching) |
39 |
| - + [Specifying Task executors](#specifying-task-executors) |
40 |
| - + [DelegateActor property](#delegateactor-property) |
| 19 | +* [Introduction](#introduction) |
| 20 | +* [Motivation](#motivation) |
| 21 | +* [Proposed solution](#proposed-solution) |
| 22 | +* [Detailed design](#detailed-design) |
| 23 | + + [A low-level design](#a-low-level-design) |
| 24 | + + [Executors](#executors) |
| 25 | + + [Serial Executors](#serial-executors) |
| 26 | + + [ExecutorJobs](#executorjobs) |
| 27 | + + [Actors with custom SerialExecutors](#actors-with-custom-serialexecutors) |
| 28 | + + [Asserting on executors](#asserting-on-executors) |
| 29 | + + [Assuming actor executors](#asserting-actor-executors) |
| 30 | + + [Default Swift Runtime Executors](#default-swift-runtime-executors) |
| 31 | +* [Source compatibility](#source-compatibility) |
| 32 | +* [Effect on ABI stability](#effect-on-abi-stability) |
| 33 | +* [Effect on API resilience](#effect-on-api-resilience) |
| 34 | +* [Alternatives considered](#alternatives-considered) |
| 35 | +* [Future Directions](#future-directions) |
| 36 | + + [Overriding the MainActor Executor](#overriding-the-mainactor-executor) |
| 37 | + + [Executor Switching Optimizations](#executor-switching) |
| 38 | + + [Specifying Task executors](#specifying-task-executors) |
| 39 | + + [DelegateActor property](#delegateactor-property) |
41 | 40 |
|
42 | 41 | ## Introduction
|
43 | 42 |
|
@@ -252,9 +251,9 @@ public struct UnownedSerialExecutor: Sendable {
|
252 | 251 |
|
253 | 252 | ### ExecutorJobs
|
254 | 253 |
|
255 |
| -A `ExecutorJob` is a representation of a chunk of of work that an executor should execute. For example, a `Task` effectively consists of a series of jobs that are enqueued onto executors, in order to run them. The name "job" was selected because we do not want to constrain this API to just "partial tasks", or tie them too closely to tasks, even though the most common type of job created by Swift concurrency are "partial tasks". |
| 254 | +An `ExecutorJob` is a representation of a chunk of of work that an executor should execute. For example, a `Task` effectively consists of a series of jobs that are enqueued onto executors, in order to run them. The name "job" was selected because we do not want to constrain this API to just "partial tasks", or tie them too closely to tasks, even though the most common type of job created by Swift concurrency are "partial tasks". |
256 | 255 |
|
257 |
| -Whenever the Swift concurrency needs to execute some piece of work, it enqueues an `UnownedExecutorJob`s on a specific executor the job should be executed on. The `UnownedExecutorJob` type is an opaque wrapper around Swift's low-level representation of such job. It cannot be meaningfully inspected, copied and must never be executed more than once. |
| 256 | +Whenever the Swift Concurrency runtime needs to execute some piece of work, it enqueues an `UnownedExecutorJob`s on a specific executor the job should be executed on. The `UnownedExecutorJob` type is an opaque wrapper around Swift's low-level representation of such job. It cannot be meaningfully inspected, copied and must never be executed more than once. |
258 | 257 |
|
259 | 258 | ```swift
|
260 | 259 | @noncopyable
|
@@ -314,7 +313,7 @@ public struct UnownedExecutorJob: Sendable, CustomStringConvertible {
|
314 | 313 |
|
315 | 314 | A job's description includes its job or task ID, that can be used to correlate it with task dumps as well as task lists in Instruments and other debugging tools (e.g. `swift-inspect`'s ). A task ID is an unique number assigned to a task, and can be useful when debugging scheduling issues, this is the same ID that is currently exposed in tools like Instruments when inspecting tasks, allowing to correlate debug logs with observations from profiling tools.
|
316 | 315 |
|
317 |
| -Eventually, an executor will want to actually run a job. It may do so right away when it is enqueued, or on some different thread, this is entirely left up to the executor to decide. Running a job is done by calling the `runSynchronously` on a `ExecutorJob` which consumes it. The same method is provided on the `UnownedExecutorJob` type, however that API is not as safe, since it cannot consume the job, and is open to running the same job multiple times accidentally which is undefined behavior. Generally, we urge developers to stick to using `ExecutorJob` APIs whenever possible, and only move to the unowned API if the noncopyable `ExecutorJob`s restrictions prove too strong to do the necessary operations on it. |
| 316 | +Eventually, an executor will want to actually run a job. It may do so right away when it is enqueued, or on some different thread, this is entirely left up to the executor to decide. Running a job is done by calling the `runSynchronously` on a `ExecutorJob` which consumes it. The same method is provided on the `UnownedExecutorJob` type, however that API is not as safe, since it cannot consume the job, and is open to running the same job multiple times accidentally, which is undefined behavior. Generally, we urge developers to stick to using `ExecutorJob` APIs whenever possible, and only move to the unowned API if the noncopyable `ExecutorJob`s restrictions prove too strong to do the necessary operations on it. |
318 | 317 |
|
319 | 318 | ```swift
|
320 | 319 | extension ExecutorJob {
|
@@ -342,7 +341,7 @@ All actors implicitly conform to the `Actor` (or `DistributedActor`) protocols,
|
342 | 341 |
|
343 | 342 | An actor's executor must conform to the `SerialExecutor` protocol, which refines the Executor protocol, and provides enough guarantees to implement the actor's mutual exclusion guarantees. In the future, `SerialExecutors` may also be extended to support "switching", which is a technique to avoid thread-switching in calls between actors whose executors are compatible to "lending" each other the currently running thread. This proposal does not cover switching semantics.
|
344 | 343 |
|
345 |
| -Actors select which serial executor they should use to run tasks is expressed by the `unownedExecutor` protocol requirement on the `Actor` and `DistributedActor` protocols: |
| 344 | +Actors select which serial executor they should use to run jobs by implementing the `unownedExecutor` protocol requirement on the `Actor` and `DistributedActor` protocols: |
346 | 345 |
|
347 | 346 | ```swift
|
348 | 347 | public protocol Actor: AnyActor {
|
@@ -407,7 +406,7 @@ public protocol DistributedActor: AnyActor {
|
407 | 406 |
|
408 | 407 | > Note: It is not possible to express this protocol requirement on `AnyActor` directly because `AnyActor` is a "marker protocol" which are not present at runtime, and cannot have protocol requirements.
|
409 | 408 |
|
410 |
| -The compiler synthesizes an implementation for this requirement for every `(distributed) actor` declaration, unless an explicit implementation is provided. The default implementation synthesized by the compiler uses the default `SerialExecutor`, that uses tha appropriate mechanism for the platform (e.g. Dispatch). Actors using this default synthesized implementation are referred to as "Default Actors", i.e. actors using the default serial executor implementation. |
| 409 | +The compiler synthesizes an implementation for this requirement for every `(distributed) actor` declaration, unless an explicit implementation is provided. The default implementation synthesized by the compiler uses the default `SerialExecutor`, which uses the appropriate mechanism for the platform (e.g. Dispatch). Actors using this default synthesized implementation are referred to as "Default Actors", i.e. actors using the default serial executor implementation. |
411 | 410 |
|
412 | 411 | Developers can customize the executor used by an actor on a declaration-by-declaration basis, by implementing this protocol requirement in an actor. For example, thanks to the `sharedUnownedExecutor` static property on `MainActor` it is possible to declare other actors which are also guaranteed to use the same serial executor (i.e. "the main thread").
|
413 | 412 |
|
@@ -482,10 +481,10 @@ A library could also provide a default implementation of such executor as well.
|
482 | 481 |
|
483 | 482 | A common pattern in event-loop heavy code–not yet using Swift Concurrency–is to ensure/verify that a synchronous piece of code is executed on the exected event-loop. Since one of the goals of making executors customizable is to allow such libraries to adopt Swift Concurrency by making such event-loops conform to `SerialExecutor`, it is useful to allow the checking if code is indeed executing on the appropriate executor, for the library to gain confidence while it is moving towards fully embracing actors and Swift concurrency.
|
484 | 483 |
|
485 |
| -For example, Swift NIO intentionally avoids synchronization checks in some synchronous methods, in order to avoid the overhead of doing so, however in DEBUG mode it performs assertions that given code is running on the expected event-loop: |
| 484 | +For example, SwiftNIO intentionally avoids synchronization checks in some synchronous methods, in order to avoid the overhead of doing so, however in DEBUG mode it performs assertions that given code is running on the expected event-loop: |
486 | 485 |
|
487 | 486 | ```swift
|
488 |
| -// Swift NIO |
| 487 | +// SwiftNIO |
489 | 488 | private var _channel: Channel
|
490 | 489 | internal var channel: Channel {
|
491 | 490 | self.eventLoop.assertInEventLoop()
|
@@ -572,7 +571,7 @@ extension DistributedActor {
|
572 | 571 | }
|
573 | 572 | ```
|
574 | 573 |
|
575 |
| -The versions of the APIs offered on `Actor` and `DistributedActor` offer better diagnostics than would be possible to implement using a plain `precondition()` implemented by developers using some `precondition(isOnExpectedExecutor"(someExecutor))` because they offer a description of the actually active executor when mismatched: |
| 574 | +The versions of the APIs offered on `Actor` and `DistributedActor` offer better diagnostics than would be possible to implement using a plain `precondition()` implemented by developers using some `precondition(isOnExpectedExecutor(someExecutor))` because they offer a description of the actually active executor when mismatched: |
576 | 575 |
|
577 | 576 | ````swift
|
578 | 577 | MainActor.preconditionIsolated()
|
@@ -868,7 +867,7 @@ These checks are likely *not* enough to to completely optimize task switching, a
|
868 | 867 |
|
869 | 868 | ### Default Swift Runtime Executors
|
870 | 869 |
|
871 |
| -Swift concurrency provides a number of default executors already, such as: |
| 870 | +Swift Concurrency provides a number of default executors already, such as: |
872 | 871 |
|
873 | 872 | - the main actor executor, which services any code annotated using @MainActor, and
|
874 | 873 | - the default global concurrent executor, which all (default) actors target by their own per-actor instantiated serial executor instances.
|
|
0 commit comments