Skip to content

Commit 55498ae

Browse files
authored
rewording the intro
1 parent ec10526 commit 55498ae

File tree

1 file changed

+14
-22
lines changed

1 file changed

+14
-22
lines changed

proposals/NNNN-custom-isolation-checking-for-serialexecutor.md

Lines changed: 14 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -9,31 +9,24 @@
99

1010
## Introduction
1111

12-
Swift introduced custom actor executors in [SE-0392: Custom Actor Executors](https://github.com/apple/swift-evolution/blob/main/proposals/0392-custom-actor-executors.md), and ever since allowed further customization of isolation and execution semantics of actors.
13-
14-
This proposal also introduced a family of assertion and assumption APIs which are able to dynamically check the isolation of a currently executing task. These APIs are:
15-
16-
- Asserting isolation context:
17-
- [`Actor/assertIsolated(_:file:line:)`](https://developer.apple.com/documentation/swift/actor/assertisolated(_:file:line:))
18-
- [`Actor/preconditionIsolated(_:file:line:)`](https://developer.apple.com/documentation/swift/actor/preconditionisolated(_:file:line:))
19-
- [`DistributedActor/assertIsolated(_:file:line:)`](https://developer.apple.com/documentation/distributed/distributedactor/preconditionisolated(_:file:line:))
20-
- [`DistributedActor/preconditionIsolated(_:file:line:)`](https://developer.apple.com/documentation/distributed/distributedactor/preconditionisolated(_:file:line:))
21-
- Assuming isolation context, and obtaining an `isolated actor` reference of the target actor
22-
- [`Actor/assumeIsolated(_:file:line:)`](https://developer.apple.com/documentation/swift/actor/assumeisolated(_:file:line:))
23-
- [`DistributedActor/assumeIsolated(_:file:line:)`](https://developer.apple.com/documentation/distributed/distributedactor/assumeisolated(_:file:line:))
12+
[SE-0392 (Custom Actor Executors)](https://github.com/apple/swift-evolution/blob/main/proposals/0392-custom-actor-executors.md) added support for custom actor executors, but its support is incomplete. Safety checks like [`Actor.assumeIsolated`](https://developer.apple.com/documentation/swift/actor/assumeisolated(_:file:line:)) work correctly when code is running on the actor through a task, but they don't work when code is scheduled to run on the actor's executor through some other mechanism. For example, if an actor uses a serial `DispatchQueue` as its executor, a function dispatched _directly_ to the queue with DispatchQueue.async cannot use `assumeIsolated` to assert that the actor is currently isolated. This proposal fixes this by allowing custom actor executors to provide their own logic for these safety checks.
2413

2514
## Motivation
2615

27-
All the above mentioned APIs rely on an internal capability of the Swift concurrency runtime to obtain the "current serial executor", and compare it against the expected executor. Additional comparison modes such as "complex equality" are also supported, which help executors that e.g. share a single thread across multiple executor instances to still be able to correctly answer the "are we on the same executor?" question when different executor *instances* are being compared, however in reality they utilize the same threading resource.
16+
The Swift concurrency runtime already provides means of dynamically tracking and checking the tasks's current executor, and APIs like `assertIsolated` and `assumeIsolated` are built on top of that functionality.
17+
18+
In those APIs the expected executor is compared with the "current" executor of a current task which is tracked by the runtime. If the code calling the comparison logic is not running within a Swift concurrency task, the comparison fails, as there are no two executors that can be compared.
2819

29-
The proposal did not account for the situation in which the Swift concurrency runtime has no notion of "current executor" though, causing the following situation to -- perhaps surprisingly -- result in runtime crashes reporting an isolation violation, while in reality, no such violation takes place in the following piece of code:
20+
This logic is not sufficient to be able to handle the situation in which code is runnin on the correct queue or thread, and mutual exclusion is properly ensured, however, the calling code is not being called from a task and therefore the check fails unexpectedly. The following example demonstrates such situation:
3021

3122
```swift
3223
import Dispatch
3324

3425
actor Caplin {
3526
let queue: DispatchSerialQueue(label: "CoolQueue")
36-
27+
28+
var num: Int // actor isolated state
29+
3730
// use the queue as this actor's `SerialExecutor`
3831
nonisolated var unownedExecutor: UnownedSerialExecutor {
3932
queue.asUnownedSerialExecutor()
@@ -44,20 +37,19 @@ actor Caplin {
4437
// guaranteed to execute on `queue`
4538
// which is the same as self's serial executor
4639
queue.assertIsolated() // CRASH: Incorrect actor executor assumption
47-
self.assertIsolated() // CRASH: Incorrect actor executor assumption
40+
self.assumeIsolated { // CRASH: Incorrect actor executor assumption
41+
num += 1
42+
}
4843
}
4944
}
5045
}
5146
```
5247

53-
One might assume that since we are specifically using the `queue` as this actor's executor... the assertions in the `connect()` function should NOT crash, however how the runtime handles this situation can be simplified to the following steps:
48+
Even though the code is executing on the correct Dispatch**Serial**Queue, the assertions trigger and we're left unable to access the actor's state, even though isolation wise it would be safe and correct to do so.
5449

55-
- try to obtain the "current executor"
56-
- since the current block of code is not executing a swift concurrency task... there is no "current executor" set in the context of `queue.async { ... }`
57-
- compare current "no executor" to the "expected executor" (the `queue` in our example)
58-
- crash, as `nil` is not the same executor as the specific `queue`
50+
This use-case is common and important enough, that the runtime actually has special cased the MainActor's isolation checking handling. A fallback check exists, which is used when the target executor is the MainActors', and the current code is not running within a task, however it is running on the *main thread*. The Swift runtime handles this special case already, however, the same problem exists for all kinds of threads which may be used as actor executors. Specifically, the problem becomes quite common when the use of `SerialDispatchQueues` as actor executors becomes prevalent, in projects migrating their pre-concurrency code-bases towards the use of actors and swift concurrency.
5951

60-
In other words, these APIs assume to be running "within Swift Concurrency", however there may be situations in which we are running on the exact serial executor, but outside of Swift Concurrency. Isolation-wise, these APIs should still be returning correctly and detecting this situation -- however they are unable to do so, without some form of cooperation with the expected `SerialExecutor`.
52+
Therefore, this proposal extends this "fallback" check mechanism to all SerialExecutors, rather than keeping it specialized to the MainActor.
6153

6254
## Proposed solution
6355

0 commit comments

Comments
 (0)