Skip to content

Commit 9a79671

Browse files
committed
add withSerialExecutor to Actor
1 parent 3229a3a commit 9a79671

File tree

1 file changed

+51
-0
lines changed

1 file changed

+51
-0
lines changed

proposals/0471-SerialExecutor-isIsolated.md

Lines changed: 51 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -135,6 +135,52 @@ The general guidance about which method to implement is to implement `isIsolatin
135135

136136
The runtime will always invoke the `isIsolatingCurrentContext` before making attempts to call `checkIsolated`, and if the prior returns either `true` or `false`, the latter (`checkIsolated`) will not be invoked at all.
137137

138+
### Checking if currently isolated to some `Actor`
139+
140+
We also introduce a way to obtain `SerialExecutor` from an `Actor`, which was previously not possible.
141+
142+
This API needs to be scoped because the lifetime of the serial executor must be tied to the Actor's lifetime:
143+
144+
```swift
145+
extension Actor {
146+
/// Perform an operation with the actor's ``SerialExecutor``.
147+
///
148+
/// This converts the actor's ``Actor/unownedExecutor`` to a ``SerialExecutor`` while
149+
/// retaining the actor for the duration of the operation. This is to ensure the lifetime
150+
/// of the executor while performing the operation.
151+
@_alwaysEmitIntoClient
152+
@available(SwiftStdlib 5.1, *)
153+
public nonisolated func withSerialExecutor<T>(_ operation: (any SerialExecutor) throws -> T) rethrows -> T
154+
155+
/// Perform an operation with the actor's ``SerialExecutor``.
156+
///
157+
/// This converts the actor's ``Actor/unownedExecutor`` to a ``SerialExecutor`` while
158+
/// retaining the actor for the duration of the operation. This is to ensure the lifetime
159+
/// of the executor while performing the operation.
160+
@_alwaysEmitIntoClient
161+
@available(SwiftStdlib 5.1, *)
162+
public nonisolated func withSerialExecutor<T>(_ operation: (any SerialExecutor) async throws -> T) async rethrows -> T
163+
164+
}
165+
```
166+
167+
This allows developers to write "warn if wrong isolation" code, before moving on to enable preconditions in a future release of a library. This gives library developers, and their adopters, time to adjust their code usage before enabling more strict validation mode in the future, for example like this:
168+
169+
```swift
170+
func something(operation: @escaping @isolated(any) () -> ()) {
171+
operation.isolation.withSerialExecutor { se in
172+
if !se.isIsolatingCurrentContext() {
173+
warn("'something' must be called from the same isolation as the operation closure is isolated to!" +
174+
"This will become a runtime crash in future releases of this library.")
175+
}
176+
}
177+
}
178+
```
179+
180+
181+
182+
This API will be backdeployed and will be available independently of runtime version of the concurrency runtime.
183+
138184
### Compatibility strategy for custom SerialExecutor authors
139185

140186
New executor implementations should prioritize implementing `isIsolatingCurrentContext` when available, using an appropriate `#if swift(>=...)` check to ensure compatibility. Otherwise, they should fall back to implementing the crashing version of this API: `checkIsolated()`.
@@ -169,7 +215,12 @@ This would be ideal, however also problematic since changing a protocol requirem
169215

170216
In order to make adoption of this new mode less painful and not cause deprecation warnings to libraries which intend to support multiple versions of Swift, the `SerialExcecutor/checkIsolated` protocol requirement remains _not_ deprecated. It may eventually become deprecated in the future, but right now we have no plans of doing so.
171217

218+
### Model the SerialExecutor lifetime dependency on Actor using `~Escapable`
219+
220+
It is currently not possible to express this lifetime dependency using `~Escapable` types, because combining `any SerialExecutor` which is an `AnyObject` constrained type, cannot be combined with `~Escapable`. Perhaps in a future revision it would be possible to offer a non-escapable serial executor in order to model this using non-escapable types, rather than a `with`-style API.
221+
172222
## Changelog
173223

224+
- added way to obtain `SerialExecutor` from `Actor` in a safe, scoped, way. This enables using the `isIsolatingCurrentContext()` API when we have an `any Actor`, e.g. from an `@isolated(any)` closure.
174225
- changed return value of `isIsolatingCurrentContext` from `Bool` to `Bool?`, where the `nil` is to be interpreted as "unknown", and the default implementation of `isIsolatingCurrentContext` now returns `nil`.
175226
- removed the manual need to signal to the runtime that the specific executor supports the new checking mode. It is now detected by the compiler and runtime, checking for the presence of a non-default implementation of the protocol requirement.

0 commit comments

Comments
 (0)