Skip to content

Commit

Permalink
Updates following pitch comments.
Browse files Browse the repository at this point in the history
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.
  • Loading branch information
al45tair committed Jan 20, 2025
1 parent 5f24faf commit d176b25
Showing 1 changed file with 76 additions and 23 deletions.
99 changes: 76 additions & 23 deletions proposals/nnnn-custom-main-and-global-executors.md
Original file line number Diff line number Diff line change
Expand Up @@ -271,11 +271,11 @@ protocol RunLoopExecutor: Executor {
}
```

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

```swift
protocol SerialRunLoopExecutor: RunLoopExecutor & SerialExecutor {
protocol MainExecutor: RunLoopExecutor & SerialExecutor & EventableExecutor {
}
```

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

extension Task {
Expand All @@ -298,7 +298,7 @@ extension Task {
///
/// Attempting to set this after the first `enqueue` on the global
/// executor is a fatal error.
public static var defaultExecutor: any Executor { get set }
public static var defaultExecutor: any TaskExecutor { get set }
}
```

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

``` swift
/// The default main executor implementation for the current platform.
public struct PlatformMainExecutor: SerialRunLoopExecutor {
public struct PlatformMainExecutor: MainExecutor {
...
}

/// The default global executor implementation for the current platform.
public struct PlatformDefaultExecutor: Executor {
public struct PlatformDefaultExecutor: TaskExecutor {
...
}
```
Expand All @@ -325,27 +325,27 @@ the `Executor` protocols:
struct ExecutorJob {
...

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

/// What kind of job this is
var kind: ExecutorJobKind
...
}
/// Kinds of schedulable jobs.
@frozen
public struct Kind: Sendable {
public typealias RawValue = UInt8

/// Kinds of schedulable jobs.
@frozen
public struct ExecutorJobKind: Sendable {
public typealias RawValue = UInt8
/// The raw job kind value.
public var rawValue: RawValue

/// The raw job kind value.
public var rawValue: RawValue
/// A task
public static let task = RawValue(0)

/// A task
public static let task = RawValue(0)
// Job kinds >= 192 are private to the implementation
public static let firstReserved = RawValue(192)
}

// Job kinds >= 192 are private to the implementation
public static let firstReserved = RawValue(192)
/// What kind of job this is
var kind: Kind
...
}
```

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

### Coalesced Event Interface

We would like custom main executors to be able to integrate with other
libraries, without tying the implementation to a specific library; in
practice, this means that the executor will need to be able to trigger
processing from some external event.

```swift
protocol EventableExecutor {

/// An opaque, executor-dependent type used to represent an event.
associatedtype Event

/// Register a new event with a given handler.
///
/// Notifying the executor of the event will cause the executor to
/// execute the handler, however the executor is free to coalesce multiple
/// event notifications, and is also free to execute the handler at a time
/// of its choosing.
///
/// Parameters
///
/// - handler: The handler to call when the event fires.
///
/// Returns a new opaque `Event`.
public func registerEvent(handler: @escaping () -> ()) -> Event

/// Deregister the given event.
///
/// After this function returns, there will be no further executions of the
/// handler for the given event.
public func deregister(event: Event)

/// Notify the executor of an event.
///
/// This will trigger, at some future point, the execution of the associated
/// event handler. Prior to that time, multiple calls to `notify` may be
/// coalesced and result in a single invocation of the event handler.
public func notify(event: Event)

}
```

Our expectation is that a library that wishes to integrate with the
main executor will register an event with the main executor, and can
then notify the main executor of that event, which will trigger the
executor to run the associated handler at an appropriate time.

The point of this interface is that a library can rely on the executor
to coalesce these events, such that the handler will be triggered once
for a potentially long series of `MainActor.executor.notify(event:)`
invocations.

## Detailed design

### `async` main code generation
Expand Down

0 comments on commit d176b25

Please sign in to comment.