You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
Copy file name to clipboardExpand all lines: proposals/NNNN-custom-isolation-checking-for-serialexecutor.md
+14-22Lines changed: 14 additions & 22 deletions
Original file line number
Diff line number
Diff line change
@@ -9,31 +9,24 @@
9
9
10
10
## Introduction
11
11
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:
[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.
24
13
25
14
## Motivation
26
15
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.
28
19
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:
30
21
31
22
```swift
32
23
importDispatch
33
24
34
25
actorCaplin {
35
26
let queue: DispatchSerialQueue(label: "CoolQueue")
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
+
}
48
43
}
49
44
}
50
45
}
51
46
```
52
47
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.
54
49
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.
59
51
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.
0 commit comments