@@ -296,7 +296,7 @@ public protocol RunLoopExecutor: Executor {
296
296
We will also add a protocol for the main actor's executor:
297
297
298
298
``` swift
299
- protocol MainExecutor : RunLoopExecutor & SerialExecutor {
299
+ protocol MainExecutor : RunLoopExecutor , SerialExecutor {
300
300
}
301
301
```
302
302
@@ -343,7 +343,7 @@ current platform.
343
343
344
344
Additionally, ` Task ` will expose a new ` currentExecutor ` property, as
345
345
well as properties for the ` preferredExecutor ` and the
346
- ` currentSchedulableExecutor ` :
346
+ ` currentSchedulingExecutor ` :
347
347
348
348
``` swift
349
349
extension Task {
@@ -362,12 +362,12 @@ extension Task {
362
362
/// Get the preferred executor for the current `Task`, if any.
363
363
public static var preferredExecutor: (any TaskExecutor)? { get }
364
364
365
- /// Get the current *schedulable * executor, if any.
365
+ /// Get the current *scheduling * executor, if any.
366
366
///
367
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
368
+ /// any executor that isn't a `SchedulingExecutor `, and as such it may
369
369
/// eventually return `nil`.
370
- public static var currentSchedulableExecutor : (any SchedulableExecutor )? { get }
370
+ public static var currentSchedulingExecutor : (any SchedulingExecutor )? { get }
371
371
}
372
372
```
373
373
@@ -384,7 +384,9 @@ struct ExecutorJob {
384
384
...
385
385
386
386
/// Execute a closure, passing it the bounds of the executor private data
387
- /// for the job.
387
+ /// for the job. The executor is responsible for ensuring that any resources
388
+ /// referenced from the private data area are cleared up prior to running the
389
+ /// job.
388
390
///
389
391
/// Parameters:
390
392
///
@@ -393,7 +395,7 @@ struct ExecutorJob {
393
395
/// Returns the result of executing the closure.
394
396
public func withUnsafeExecutorPrivateData <R , E >(body : (UnsafeMutableRawBufferPointer) throws (E) -> R) throws (E) -> R
395
397
396
- /// Kinds of schedulable jobs.
398
+ /// Kinds of scheduling jobs.
397
399
@frozen
398
400
public struct Kind : Sendable , RawRepresentable {
399
401
public typealias RawValue = UInt8
@@ -416,7 +418,7 @@ struct ExecutorJob {
416
418
417
419
Finally, jobs of type ` ExecutorJob.Kind.task ` have the ability to
418
420
allocate task memory, using a stack disciplined allocator; this memory
419
- is automatically released when the task itself is released .
421
+ must be released _ in reverse order _ before the job is executed .
420
422
421
423
Rather than require users to test the job kind to discover this, which
422
424
would mean that they would not be able to use allocation on new job
@@ -437,13 +439,11 @@ extension ExecutorJob {
437
439
/// A job-local stack-disciplined allocator.
438
440
///
439
441
/// This can be used to allocate additional data required by an
440
- /// executor implementation; memory allocated in this manner will
441
- /// be released automatically when the job is disposed of by the
442
- /// runtime.
442
+ /// executor implementation; memory allocated in this manner must
443
+ /// be released by the executor before the job is executed.
443
444
///
444
445
/// N.B. Because this allocator is stack disciplined, explicitly
445
- /// deallocating memory will also deallocate all memory allocated
446
- /// after the block being deallocated.
446
+ /// deallocating memory out-of-order will cause your program to abort.
447
447
struct LocalAllocator {
448
448
449
449
/// Allocate a specified number of bytes of uninitialized memory.
@@ -457,19 +457,16 @@ extension ExecutorJob {
457
457
public func allocate <T >(capacity : Int , as : T.Type )
458
458
-> UnsafeMutableBufferPointer < T>?
459
459
460
- /// Deallocate previously allocated memory. Note that the task
461
- /// allocator is stack disciplined, so if you deallocate a block of
462
- /// memory, all memory allocated after that block is also deallocated.
460
+ /// Deallocate previously allocated memory. You must do this in
461
+ /// reverse order of allocations, prior to running the job.
463
462
public func deallocate (_ buffer : UnsafeMutableRawBufferPointer? )
464
463
465
- /// Deallocate previously allocated memory. Note that the task
466
- /// allocator is stack disciplined, so if you deallocate a block of
467
- /// memory, all memory allocated after that block is also deallocated.
464
+ /// Deallocate previously allocated memory. You must do this in
465
+ /// reverse order of allocations, prior to running the job.
468
466
public func deallocate <T >(_ pointer : UnsafeMutablePointer <T>? )
469
467
470
- /// Deallocate previously allocated memory. Note that the task
471
- /// allocator is stack disciplined, so if you deallocate a block of
472
- /// memory, all memory allocated after that block is also deallocated.
468
+ /// Deallocate previously allocated memory. You must do this in
469
+ /// reverse order of allocations, prior to running the job.
473
470
public func deallocate <T >(_ buffer : UnsafeMutableBufferPointer <T>? )
474
471
475
472
}
@@ -494,23 +491,28 @@ if let chunk = job.allocator?.allocate(capacity: 1024) {
494
491
}
495
492
```
496
493
497
- We will also add a ` SchedulableExecutor ` protocol as well as a way to
494
+ This feature is useful for executors that need to store additional
495
+ data alongside jobs that they currently have queued up. It is worth
496
+ re-emphasising that the data needs to be released, in reverse order
497
+ of allocation, prior to execution of the job to which it is attached.
498
+
499
+ We will also add a ` SchedulingExecutor ` protocol as well as a way to
498
500
get it efficiently from an ` Executor ` :
499
501
500
502
``` swift
501
503
protocol Executor {
502
504
...
503
- /// Return this executable as a SchedulableExecutor , or nil if that is
505
+ /// Return this executable as a SchedulingExecutor , or nil if that is
504
506
/// unsupported.
505
507
///
506
508
/// Executors can implement this method explicitly to avoid the use of
507
509
/// a potentially expensive runtime cast.
508
510
@available (SwiftStdlib 6.2, * )
509
- var asSchedulable: AsSchedulable ? { get }
511
+ var asSchedulingExecutor: ( any SchedulingExecutor) ? { get }
510
512
...
511
513
}
512
514
513
- protocol SchedulableExecutor : Executor {
515
+ protocol SchedulingExecutor : Executor {
514
516
...
515
517
/// Enqueue a job to run after a specified delay.
516
518
///
@@ -565,101 +567,116 @@ protocol as follows:
565
567
``` swift
566
568
protocol Clock {
567
569
...
568
- /// The traits associated with this clock instance.
569
- var traits: ClockTraits { get }
570
-
571
- /// Convert a Clock-specific Duration to a Swift Duration
572
- ///
573
- /// Some clocks may define `C.Duration` to be something other than a
574
- /// `Swift.Duration`, but that makes it tricky to convert timestamps
575
- /// between clocks, which is something we want to be able to support.
576
- /// This method will convert whatever `C.Duration` is to a `Swift.Duration`.
570
+ /// Run the given job on an unspecified executor at some point
571
+ /// after the given instant.
577
572
///
578
573
/// Parameters:
579
574
///
580
- /// - from duration: The `Duration` to convert
575
+ /// - job: The job we wish to run
576
+ /// - at instant: The time at which we would like it to run.
577
+ /// - tolerance: The ideal maximum delay we are willing to tolerate.
581
578
///
582
- /// Returns: A `Swift.Duration` representing the equivalent duration, or
583
- /// `nil` if this function is not supported.
584
- func convert (from duration : Duration) -> Swift.Duration?
579
+ func run (_ job : consuming ExecutorJob,
580
+ at instant : Instant, tolerance : Duration? )
585
581
586
- /// Convert a Swift Duration to a Clock-specific Duration
582
+ /// Enqueue the given job on the specified executor at some point after the
583
+ /// given instant.
587
584
///
588
- /// Parameters:
589
- ///
590
- /// - from duration: The `Swift.Duration` to convert.
591
- ///
592
- /// Returns: A `Duration` representing the equivalent duration, or
593
- /// `nil` if this function is not supported.
594
- func convert (from duration : Swift.Duration) -> Duration?
595
-
596
- /// Convert an `Instant` from some other clock's `Instant`
585
+ /// The default implementation uses the `run` method to trigger a job that
586
+ /// does `executor.enqueue(job)`. If a particular `Clock` knows that the
587
+ /// executor it has been asked to use is the same one that it will run jobs
588
+ /// on, it can short-circuit this behaviour and directly use `run` with
589
+ /// the original job.
597
590
///
598
591
/// Parameters:
599
592
///
600
- /// - instant: The instant to convert.
601
- // - from clock: The clock to convert from.
593
+ /// - job: The job we wish to run
594
+ /// - on executor: The executor on which we would like it to run.
595
+ /// - at instant: The time at which we would like it to run.
596
+ /// - tolerance: The ideal maximum delay we are willing to tolerate.
602
597
///
603
- /// Returns: An `Instant` representing the equivalent instant, or
604
- /// `nil` if this function is not supported.
605
- func convert <OtherClock : Clock >(instant : OtherClock.Instant,
606
- from clock : OtherClock) -> Instant?
598
+ func enqueue (_ job : consuming ExecutorJob,
599
+ on executor : some Executor,
600
+ at instant : Instant, tolerance : Duration? )
607
601
...
608
602
}
609
603
```
610
604
611
- If your ` Clock ` uses ` Swift.Duration ` as its ` Duration ` type, the
612
- ` convert(from duration:) ` methods will be implemented for you. There
613
- is also a default implementation of the ` Instant ` conversion method
614
- that makes use of the ` Duration ` conversion methods .
605
+ There is a default implementation of the ` enqueue ` method on ` Clock ` ,
606
+ which calls the ` run ` method; if you attempt to use a ` Clock ` with an
607
+ executor that does not understand it, and that ` Clock ` does not
608
+ implement the ` run ` method, you will get a fatal error at runtime .
615
609
616
- The ` traits ` property is of type ` ClockTraits ` , which is an
617
- ` OptionSet ` as follows:
610
+ Executors that do not specifically recognise a particular clock may
611
+ choose instead to have their ` enqueue(..., clock:) ` methods call the
612
+ clock's ` enqueue() ` method; this will allow the clock to make an
613
+ appropriate decision as to how to proceed.
618
614
619
- ``` swift
620
- /// Represents traits of a particular Clock implementation.
621
- ///
622
- /// Clocks may be of a number of different varieties; executors will likely
623
- /// have specific clocks that they can use to schedule jobs, and will
624
- /// therefore need to be able to convert timestamps to an appropriate clock
625
- /// when asked to enqueue a job with a delay or deadline.
626
- ///
627
- /// Choosing a clock in general requires the ability to tell which of their
628
- /// clocks best matches the clock that the user is trying to specify a
629
- /// time or delay in. Executors are expected to do this on a best effort
630
- /// basis.
631
- @available (SwiftStdlib 6.2, * )
632
- public struct ClockTraits : OptionSet {
633
- public let rawValue: UInt32
615
+ We will also add a way to test if an executor is the main executor:
634
616
635
- public init (rawValue : UInt32 )
617
+ ``` swift
618
+ protocol Executor {
619
+ ...
620
+ /// `true` if this is the main executor.
621
+ var isMainExecutor: Bool { get }
622
+ ...
623
+ }
624
+ ```
636
625
637
- /// Clocks with this trait continue running while the machine is asleep.
638
- public static let continuous = ...
626
+ Finally, we will expose the following built-in executor
627
+ implementations:
639
628
640
- /// Indicates that a clock's time will only ever increase.
641
- public static let monotonic = ...
629
+ ``` swift
630
+ /// A Dispatch-based main executor (not on Embedded or WASI)
631
+ @available (StdlibDeploymentTarget 6.2, * )
632
+ public class DispatchMainExecutor : MainExecutor ,
633
+ SchedulingExecutor ,
634
+ @unchecked Sendable {
635
+ ...
636
+ }
642
637
643
- /// Clocks with this trait are tied to "wall time".
644
- public static let wallTime = ...
638
+ /// A Dispatch-based `TaskExecutor` (not on Embedded or WASI)
639
+ @available (StdlibDeploymentTarget 6.2, * )
640
+ public class DispatchGlobalTaskExecutor : TaskExecutor ,
641
+ SchedulingExecutor ,
642
+ @unchecked Sendable {
643
+ ...
645
644
}
646
- ```
647
645
648
- Clock traits can be used by executor implementations to select the
649
- most appropriate clock that they know how to wait on; they can then
650
- use the ` convert() ` method above to convert the ` Instant ` or
651
- ` Duration ` to that clock in order to actually enqueue a job.
646
+ /// A CFRunLoop-based main executor (Apple platforms only)
647
+ @available (StdlibDeploymentTarget 6.2, * )
648
+ public final class CFMainExecutor : DispatchMainExecutor ,
649
+ @unchecked Sendable {
650
+ ...
651
+ }
652
652
653
- ` ContinuousClock ` and ` SuspendingClock ` will be updated to support
654
- these new features.
653
+ /// A `TaskExecutor` to match `CFMainExecutor` (Apple platforms only)
654
+ @available (StdlibDeploymentTarget 6.2, * )
655
+ public final class CFTaskExecutor : DispatchGlobalTaskExecutor ,
656
+ @unchecked Sendable {
657
+ ...
658
+ }
655
659
656
- We will also add a way to test if an executor is the main executor:
660
+ /// A co-operative executor that can be used as the main executor or as a
661
+ /// task executor. Tasks scheduled on this executor will run on the thread
662
+ /// that called `run()`.
663
+ ///
664
+ /// Note that this executor will not be thread-safe on Embedded Swift.
665
+ @available (StdlibDeploymentTarget 6.2, * )
666
+ class CooperativeExecutor : MainExecutor ,
667
+ TaskExecutor ,
668
+ SchedulingExecutor ,
669
+ @unchecked Sendable {
670
+ ...
671
+ }
657
672
658
- ``` swift
659
- protocol Executor {
673
+ /// A main executor that calls fatalError().
674
+ class UnimplementedMainExecutor : MainExecutor , @ unchecked Sendable {
660
675
...
661
- /// `true` if this is the main executor.
662
- var isMainExecutor: Bool { get }
676
+ }
677
+
678
+ /// A task executor that calls fatalError().
679
+ class UnimplementedTaskExecutor : TaskExecutor , @unchecked Sendable {
663
680
...
664
681
}
665
682
```
@@ -702,9 +719,7 @@ We will also add an `executorFactory` option in SwiftPM's
702
719
` swiftSettings ` to let people specify the executor factory in their
703
720
package manifests.
704
721
705
- ## Detailed design
706
-
707
- ### ` async ` main code generation
722
+ ## ` async ` main code generation
708
723
709
724
The compiler's code generation for ` async ` main functions will change
710
725
to something like
@@ -859,13 +874,26 @@ knowledge of those libraries.
859
874
While a good idea, it was decided that this would be better dealt with
860
875
as a separate proposal.
861
876
862
- ### Putting the new Clock-based enqueue functions into a protocol
877
+ ### Putting the new ` Clock ` -based enqueue functions into a protocol
863
878
864
879
It would be cleaner to have the new Clock-based enqueue functions in a
865
880
separate ` SchedulingExecutor ` protocol. However, if we did that, we
866
881
would need to add ` as? SchedulingExecutor ` runtime casts in various
867
882
places in the code, and dynamic casts can be expensive.
868
883
884
+ ### Adding special support for canonicalizing ` Clock ` s
885
+
886
+ There are situations where you might create a derived ` Clock ` , that is
887
+ implemented under the covers by reference to some other clock. One
888
+ way to support that might be to add a ` canonicalClock ` property that
889
+ you can fetch to obtain the underlying clock, then provide conversion
890
+ functions to convert ` Instant ` and ` Duration ` values as appropriate.
891
+
892
+ After implementing this, it became apparent that it wasn't really
893
+ necessary and complicated the API without providing any significant
894
+ additional capability. A derived ` Clock ` can simply implement the
895
+ ` run ` and/or ` enqueue ` methods instead.
896
+
869
897
## Acknowledgments
870
898
871
899
Thanks to Cory Benfield, Franz Busch, David Greenaway, Rokhini Prabhu,
0 commit comments