Skip to content

Commit d176b25

Browse files
committed
Updates following pitch comments.
Use `any TaskExecutor` instead of `any Executor` for `Task.defaultExecutor`. Rename `ExecutorJobKind` to `ExecutorJob.Kind`. Add `EventableExecutor`; replace `SerialRunLoopExecutor` with `MainExecutor`, then make `MainActor` and `PlatformMainExecutor` use the new protocol.
1 parent 5f24faf commit d176b25

File tree

1 file changed

+76
-23
lines changed

1 file changed

+76
-23
lines changed

proposals/nnnn-custom-main-and-global-executors.md

+76-23
Original file line numberDiff line numberDiff line change
@@ -271,11 +271,11 @@ protocol RunLoopExecutor: Executor {
271271
}
272272
```
273273

274-
We will also add a protocol for `RunLoopExecutor`s that are also
275-
`SerialExecutors`:
274+
We will also add a protocol for the main actor's executor (see later
275+
for details of `EventableExecutor` and why it exists):
276276

277277
```swift
278-
protocol SerialRunLoopExecutor: RunLoopExecutor & SerialExecutor {
278+
protocol MainExecutor: RunLoopExecutor & SerialExecutor & EventableExecutor {
279279
}
280280
```
281281

@@ -289,7 +289,7 @@ extension MainActor {
289289
///
290290
/// Attempting to set this after the first `enqueue` on the main
291291
/// executor is a fatal error.
292-
public static var executor: any SerialRunLoopExecutor { get set }
292+
public static var executor: any MainExecutor { get set }
293293
}
294294

295295
extension Task {
@@ -298,7 +298,7 @@ extension Task {
298298
///
299299
/// Attempting to set this after the first `enqueue` on the global
300300
/// executor is a fatal error.
301-
public static var defaultExecutor: any Executor { get set }
301+
public static var defaultExecutor: any TaskExecutor { get set }
302302
}
303303
```
304304

@@ -307,12 +307,12 @@ exposed with the names below:
307307

308308
``` swift
309309
/// The default main executor implementation for the current platform.
310-
public struct PlatformMainExecutor: SerialRunLoopExecutor {
310+
public struct PlatformMainExecutor: MainExecutor {
311311
...
312312
}
313313

314314
/// The default global executor implementation for the current platform.
315-
public struct PlatformDefaultExecutor: Executor {
315+
public struct PlatformDefaultExecutor: TaskExecutor {
316316
...
317317
}
318318
```
@@ -325,27 +325,27 @@ the `Executor` protocols:
325325
struct ExecutorJob {
326326
...
327327

328-
/// Storage reserved for the scheduler (exactly two UInts in size)
329-
var schedulerPrivate: some Collection<UInt>
328+
/// Storage reserved for the executor
329+
var executorPrivate: (UInt, UInt)
330330

331-
/// What kind of job this is
332-
var kind: ExecutorJobKind
333-
...
334-
}
331+
/// Kinds of schedulable jobs.
332+
@frozen
333+
public struct Kind: Sendable {
334+
public typealias RawValue = UInt8
335335

336-
/// Kinds of schedulable jobs.
337-
@frozen
338-
public struct ExecutorJobKind: Sendable {
339-
public typealias RawValue = UInt8
336+
/// The raw job kind value.
337+
public var rawValue: RawValue
340338

341-
/// The raw job kind value.
342-
public var rawValue: RawValue
339+
/// A task
340+
public static let task = RawValue(0)
343341

344-
/// A task
345-
public static let task = RawValue(0)
342+
// Job kinds >= 192 are private to the implementation
343+
public static let firstReserved = RawValue(192)
344+
}
346345

347-
// Job kinds >= 192 are private to the implementation
348-
public static let firstReserved = RawValue(192)
346+
/// What kind of job this is
347+
var kind: Kind
348+
...
349349
}
350350
```
351351

@@ -422,6 +422,59 @@ extension Task {
422422
If this option is enabled, an Embedded Swift program that wishes to
423423
customize executor behaviour will have to use the C API.
424424

425+
### Coalesced Event Interface
426+
427+
We would like custom main executors to be able to integrate with other
428+
libraries, without tying the implementation to a specific library; in
429+
practice, this means that the executor will need to be able to trigger
430+
processing from some external event.
431+
432+
```swift
433+
protocol EventableExecutor {
434+
435+
/// An opaque, executor-dependent type used to represent an event.
436+
associatedtype Event
437+
438+
/// Register a new event with a given handler.
439+
///
440+
/// Notifying the executor of the event will cause the executor to
441+
/// execute the handler, however the executor is free to coalesce multiple
442+
/// event notifications, and is also free to execute the handler at a time
443+
/// of its choosing.
444+
///
445+
/// Parameters
446+
///
447+
/// - handler: The handler to call when the event fires.
448+
///
449+
/// Returns a new opaque `Event`.
450+
public func registerEvent(handler: @escaping () -> ()) -> Event
451+
452+
/// Deregister the given event.
453+
///
454+
/// After this function returns, there will be no further executions of the
455+
/// handler for the given event.
456+
public func deregister(event: Event)
457+
458+
/// Notify the executor of an event.
459+
///
460+
/// This will trigger, at some future point, the execution of the associated
461+
/// event handler. Prior to that time, multiple calls to `notify` may be
462+
/// coalesced and result in a single invocation of the event handler.
463+
public func notify(event: Event)
464+
465+
}
466+
```
467+
468+
Our expectation is that a library that wishes to integrate with the
469+
main executor will register an event with the main executor, and can
470+
then notify the main executor of that event, which will trigger the
471+
executor to run the associated handler at an appropriate time.
472+
473+
The point of this interface is that a library can rely on the executor
474+
to coalesce these events, such that the handler will be triggered once
475+
for a potentially long series of `MainActor.executor.notify(event:)`
476+
invocations.
477+
425478
## Detailed design
426479

427480
### `async` main code generation

0 commit comments

Comments
 (0)