Skip to content

Commit 75b1b89

Browse files
committed
Remove EventableExecutor, further updates from pitch.
Remove `EventableExecutor`; we can come back to that later. Update documentation for `currentExecutor` and add `preferredExecutor` and `currentSchedulableExecutor`. Move the clock-based enqueuing to a separate `SchedulableExecutor` protocol, and provide an efficient way to get it. This means we don't need the `supportsScheduling` property. Back `ClockTraits` with `UInt32`.
1 parent 8e0fe92 commit 75b1b89

File tree

1 file changed

+48
-77
lines changed

1 file changed

+48
-77
lines changed

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

Lines changed: 48 additions & 77 deletions
Original file line numberDiff line numberDiff line change
@@ -293,11 +293,10 @@ public protocol RunLoopExecutor: Executor {
293293
}
294294
```
295295

296-
We will also add a protocol for the main actor's executor (see later
297-
for details of `EventableExecutor` and why it exists):
296+
We will also add a protocol for the main actor's executor:
298297

299298
```swift
300-
protocol MainExecutor: RunLoopExecutor & SerialExecutor & EventableExecutor {
299+
protocol MainExecutor: RunLoopExecutor & SerialExecutor {
301300
}
302301
```
303302

@@ -342,13 +341,33 @@ along with a default implementation of `ExecutorFactory` called
342341
`PlatformExecutorFactory` that sets the default executors for the
343342
current platform.
344343

345-
Additionally, `Task` will expose a new `currentExecutor` property:
344+
Additionally, `Task` will expose a new `currentExecutor` property, as
345+
well as properties for the `preferredExecutor` and the
346+
`currentSchedulableExecutor`:
346347

347348
```swift
348349
extension Task {
349350
/// Get the current executor; this is the executor that the currently
350351
/// executing task is executing on.
351-
public static var currentExecutor: (any Executor)? { get }
352+
///
353+
/// This will return, in order of preference:
354+
///
355+
/// 1. The custom executor associated with an `Actor` on which we are
356+
/// currently running, or
357+
/// 2. The preferred executor for the currently executing `Task`, or
358+
/// 3. The task executor for the current thread
359+
/// 4. The default executor.
360+
public static var currentExecutor: any Executor { get }
361+
362+
/// Get the preferred executor for the current `Task`, if any.
363+
public static var preferredExecutor: (any TaskExecutor)? { get }
364+
365+
/// Get the current *schedulable* executor, if any.
366+
///
367+
/// This follows the same logic as `currentExecutor`, except that it ignores
368+
/// any executor that isn't a `SchedulableExecutor`, and as such it may
369+
/// eventually return `nil`.
370+
public static var currentSchedulableExecutor: (any SchedulableExecutor)? { get }
352371
}
353372
```
354373

@@ -475,21 +494,24 @@ if let chunk = job.allocator?.allocate(capacity: 1024) {
475494
}
476495
```
477496

478-
We will also round-out the `Executor` protocol with some `Clock`-based
479-
APIs to enqueue after a delay:
497+
We will also add a `SchedulableExecutor` protocol as well as a way to
498+
get it efficiently from an `Executor`:
480499

481500
```swift
482501
protocol Executor {
483502
...
484-
/// `true` if this Executor supports scheduling.
503+
/// Return this executable as a SchedulableExecutor, or nil if that is
504+
/// unsupported.
485505
///
486-
/// This will default to false. If you attempt to use the delayed
487-
/// enqueuing functions on an executor that does not support scheduling,
488-
/// the default executor will be used to do the scheduling instead,
489-
/// unless the default executor does not support scheduling in which
490-
/// case you will get a fatal error.
491-
var supportsScheduling: Bool { get }
506+
/// Executors can implement this method explicitly to avoid the use of
507+
/// a potentially expensive runtime cast.
508+
@available(SwiftStdlib 6.2, *)
509+
var asSchedulable: AsSchedulable? { get }
510+
...
511+
}
492512

513+
protocol SchedulableExecutor: Executor {
514+
...
493515
/// Enqueue a job to run after a specified delay.
494516
///
495517
/// You need only implement one of the two enqueue functions here;
@@ -537,14 +559,6 @@ APIs to get both of them working; there is a default implementation
537559
that will do the necessary mathematics for you to implement the other
538560
one.
539561

540-
If you try to call the `Clock`-based `enqueue` APIs on an executor
541-
that does not declare support for them (by returning `true` from its
542-
`supportsScheduling` property), the runtime will raise a fatal error.
543-
544-
(These functions have been added to the `Executor` protocol directly
545-
rather than adding a separate protocol to avoid having to do a dynamic
546-
cast at runtime, which is a relatively slow operation.)
547-
548562
To support these `Clock`-based APIs, we will add to the `Clock`
549563
protocol as follows:
550564

@@ -616,9 +630,9 @@ The `traits` property is of type `ClockTraits`, which is an
616630
/// basis.
617631
@available(SwiftStdlib 6.2, *)
618632
public struct ClockTraits: OptionSet {
619-
public let rawValue: Int32
633+
public let rawValue: UInt32
620634

621-
public init(rawValue: Int32)
635+
public init(rawValue: UInt32)
622636

623637
/// Clocks with this trait continue running while the machine is asleep.
624638
public static let continuous = ...
@@ -661,59 +675,6 @@ We will not be able to support the new `Clock`-based `enqueue` APIs on
661675
Embedded Swift at present because it does not allow protocols to
662676
contain generic functions.
663677

664-
### Coalesced Event Interface
665-
666-
We would like custom main executors to be able to integrate with other
667-
libraries, without tying the implementation to a specific library; in
668-
practice, this means that the executor will need to be able to trigger
669-
processing from some external event.
670-
671-
```swift
672-
protocol EventableExecutor {
673-
674-
/// An opaque, executor-dependent type used to represent an event.
675-
associatedtype Event
676-
677-
/// Register a new event with a given handler.
678-
///
679-
/// Notifying the executor of the event will cause the executor to
680-
/// execute the handler, however the executor is free to coalesce multiple
681-
/// event notifications, and is also free to execute the handler at a time
682-
/// of its choosing.
683-
///
684-
/// Parameters
685-
///
686-
/// - handler: The handler to call when the event fires.
687-
///
688-
/// Returns a new opaque `Event`.
689-
public func registerEvent(handler: @escaping @Sendable () -> ()) -> Event
690-
691-
/// Deregister the given event.
692-
///
693-
/// After this function returns, there will be no further executions of the
694-
/// handler for the given event.
695-
public func deregister(event: Event)
696-
697-
/// Notify the executor of an event.
698-
///
699-
/// This will trigger, at some future point, the execution of the associated
700-
/// event handler. Prior to that time, multiple calls to `notify` may be
701-
/// coalesced and result in a single invocation of the event handler.
702-
public func notify(event: Event)
703-
704-
}
705-
```
706-
707-
Our expectation is that a library that wishes to integrate with the
708-
main executor will register an event with the main executor, and can
709-
then notify the main executor of that event, which will trigger the
710-
executor to run the associated handler at an appropriate time.
711-
712-
The point of this interface is that a library can rely on the executor
713-
to coalesce these events, such that the handler will be triggered once
714-
for a potentially long series of `MainActor.executor.notify(event:)`
715-
invocations.
716-
717678
### Overriding the main and default executors
718679

719680
Setting the executors directly is tricky because they might already be
@@ -888,6 +849,16 @@ break for places where Swift Concurrency already runs, because some
888849
existing code already knows that it is not really asynchronous until
889850
the first `await` in the main entry point.
890851

852+
### Adding a coalesced event interface
853+
854+
A previous revision of this proposal included an `EventableExecutor`
855+
interface, which could be used to tie other libraries into a custom
856+
executor without the custom executor needing to have specific
857+
knowledge of those libraries.
858+
859+
While a good idea, it was decided that this would be better dealt with
860+
as a separate proposal.
861+
891862
### Putting the new Clock-based enqueue functions into a protocol
892863

893864
It would be cleaner to have the new Clock-based enqueue functions in a

0 commit comments

Comments
 (0)