@@ -16,6 +16,13 @@ import Swift
16
16
@available ( SwiftStdlib 5 . 1 , * )
17
17
public protocol Executor : AnyObject , Sendable {
18
18
19
+ /// This Executor type as a schedulable executor.
20
+ ///
21
+ /// If the conforming type also conforms to `SchedulableExecutor`, then this is
22
+ /// bound to `Self`. Otherwise, it is an uninhabited type (such as Never).
23
+ @available ( SwiftStdlib 6 . 2 , * )
24
+ associatedtype AsSchedulable : SchedulableExecutor = SchedulableExecutorNever
25
+
19
26
// Since lack move-only type support in the SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY configuration
20
27
// Do not deprecate the UnownedJob enqueue in that configuration just yet - as we cannot introduce the replacements.
21
28
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
@@ -46,16 +53,25 @@ public protocol Executor: AnyObject, Sendable {
46
53
@available ( SwiftStdlib 6 . 2 , * )
47
54
var isMainExecutor : Bool { get }
48
55
#endif
56
+ }
49
57
50
- /// `true` if this Executor supports scheduling.
51
- ///
52
- /// This will default to false. If you attempt to use the delayed
53
- /// enqueuing functions on an executor that does not support scheduling,
54
- /// the default executor will be used to do the scheduling instead,
55
- /// unless the default executor does not support scheduling in which
56
- /// case you will get a fatal error.
57
- @available ( SwiftStdlib 6 . 2 , * )
58
- var supportsScheduling : Bool { get }
58
+ /// Uninhabited class type to indicate we don't support scheduling.
59
+ @available ( SwiftStdlib 6 . 2 , * )
60
+ public class SchedulableExecutorNever {
61
+ private init ( _ n: Never ) { }
62
+ }
63
+
64
+ @available ( SwiftStdlib 6 . 2 , * )
65
+ extension SchedulableExecutorNever : SchedulableExecutor , @unchecked Sendable {
66
+
67
+ public func enqueue( _ job: consuming ExecutorJob ) {
68
+ fatalError ( " This should never be reached " )
69
+ }
70
+
71
+ }
72
+
73
+ @available ( SwiftStdlib 6 . 2 , * )
74
+ public protocol SchedulableExecutor : Executor where Self. AsSchedulable == Self {
59
75
60
76
#if !$Embedded
61
77
@@ -101,6 +117,24 @@ public protocol Executor: AnyObject, Sendable {
101
117
clock: C )
102
118
103
119
#endif // !$Embedded
120
+
121
+ }
122
+
123
+ extension Executor {
124
+ /// Return this executable as a SchedulableExecutor, or nil if that is
125
+ /// unsupported.
126
+ @available ( SwiftStdlib 6 . 2 , * )
127
+ var asSchedulable : AsSchedulable ? {
128
+ #if !$Embedded
129
+ if Self . self == AsSchedulable . self {
130
+ return _identityCast ( self , to: AsSchedulable . self)
131
+ } else {
132
+ return nil
133
+ }
134
+ #else
135
+ return nil
136
+ #endif
137
+ }
104
138
}
105
139
106
140
extension Executor {
@@ -115,7 +149,6 @@ extension Executor where Self: Equatable {
115
149
internal var _isComplexEquality : Bool { true }
116
150
}
117
151
118
- // Delay support
119
152
extension Executor {
120
153
121
154
#if !$Embedded
@@ -125,10 +158,11 @@ extension Executor {
125
158
public var isMainExecutor : Bool { false }
126
159
#endif
127
160
128
- // This defaults to `false` so that existing third-party TaskExecutor
129
- // implementations will work as expected.
130
- @available ( SwiftStdlib 6 . 2 , * )
131
- public var supportsScheduling : Bool { false }
161
+ }
162
+
163
+ // Delay support
164
+ @available ( SwiftStdlib 6 . 2 , * )
165
+ extension SchedulableExecutor {
132
166
133
167
#if !$Embedded
134
168
@@ -137,10 +171,6 @@ extension Executor {
137
171
after delay: C . Duration ,
138
172
tolerance: C . Duration ? = nil ,
139
173
clock: C ) {
140
- if !supportsScheduling {
141
- fatalError ( " Executor \( self ) does not support scheduling " )
142
- }
143
-
144
174
// If you crash here with a mutual recursion, it's because you didn't
145
175
// implement one of these two functions
146
176
enqueue ( job, at: clock. now. advanced ( by: delay) ,
@@ -152,10 +182,6 @@ extension Executor {
152
182
at instant: C . Instant ,
153
183
tolerance: C . Duration ? = nil ,
154
184
clock: C ) {
155
- if !supportsScheduling {
156
- fatalError ( " Executor \( self ) does not support scheduling " )
157
- }
158
-
159
185
// If you crash here with a mutual recursion, it's because you didn't
160
186
// implement one of these two functions
161
187
enqueue ( job, after: clock. now. duration ( to: instant) ,
@@ -493,9 +519,9 @@ public protocol RunLoopExecutor: Executor {
493
519
///
494
520
/// Parameters:
495
521
///
496
- /// - until condition: A closure that returns `true` if the run loop should
497
- /// stop.
498
- func run ( until condition: ( ) -> Bool ) throws
522
+ /// - condition: A closure that returns `true` if the run loop should
523
+ /// stop.
524
+ func runUntil ( _ condition: ( ) -> Bool ) throws
499
525
500
526
/// Signal to the run loop to stop running and return.
501
527
///
@@ -517,9 +543,7 @@ extension RunLoopExecutor {
517
543
}
518
544
519
545
520
- /// Represents an event; we don't want to allocate, so we can't use
521
- /// a protocol here and use `any Event`. Instead of doing that, wrap
522
- /// an `Int` (which is pointer-sized) in a `struct`.
546
+ /// Represents an event registered with an `EventableExecutor`.
523
547
@available ( SwiftStdlib 6 . 2 , * )
524
548
public struct ExecutorEvent : Identifiable , Comparable , Sendable {
525
549
public typealias ID = Int
@@ -645,18 +669,58 @@ extension Task where Success == Never, Failure == Never {
645
669
extension Task where Success == Never , Failure == Never {
646
670
/// Get the current executor; this is the executor that the currently
647
671
/// executing task is executing on.
672
+ ///
673
+ /// This will return, in order of preference:
674
+ ///
675
+ /// 1. The custom executor associated with an `Actor` on which we are
676
+ /// currently running, or
677
+ /// 2. The preferred executor for the currently executing `Task`, or
678
+ /// 3. The task executor for the current thread
679
+ ///
680
+ /// If none of these exist, this property will be `nil`.
648
681
@available ( SwiftStdlib 6 . 2 , * )
649
682
@_unavailableInEmbedded
650
683
public static var currentExecutor : ( any Executor ) ? {
651
- if let taskExecutor = unsafe _getPreferredTaskExecutor( ) . asTaskExecutor ( ) {
652
- return taskExecutor
653
- } else if let activeExecutor = unsafe _getActiveExecutor( ) . asSerialExecutor ( ) {
684
+ if let activeExecutor = unsafe _getActiveExecutor( ) . asSerialExecutor ( ) {
654
685
return activeExecutor
686
+ } else if let taskExecutor = unsafe _getPreferredTaskExecutor( ) . asTaskExecutor ( ) {
687
+ return taskExecutor
655
688
} else if let taskExecutor = unsafe _getCurrentTaskExecutor( ) . asTaskExecutor ( ) {
656
689
return taskExecutor
657
690
}
658
691
return nil
659
692
}
693
+
694
+ /// Get the preferred executor for the current `Task`, if any.
695
+ @available ( SwiftStdlib 6 . 2 , * )
696
+ public static var preferredExecutor : ( any TaskExecutor ) ? {
697
+ if let taskExecutor = unsafe _getPreferredTaskExecutor( ) . asTaskExecutor ( ) {
698
+ return taskExecutor
699
+ }
700
+ return nil
701
+ }
702
+
703
+ /// Get the current *schedulable* executor, if any.
704
+ ///
705
+ /// This follows the same logic as `currentExecutor`, except that it ignores
706
+ /// any executor that isn't a `SchedulableExecutor`.
707
+ @available ( SwiftStdlib 6 . 2 , * )
708
+ @_unavailableInEmbedded
709
+ public static var currentSchedulableExecutor : ( any SchedulableExecutor ) ? {
710
+ if let activeExecutor = unsafe _getActiveExecutor( ) . asSerialExecutor ( ) ,
711
+ let schedulable = activeExecutor. asSchedulable {
712
+ return schedulable
713
+ }
714
+ if let taskExecutor = unsafe _getPreferredTaskExecutor( ) . asTaskExecutor ( ) ,
715
+ let schedulable = taskExecutor. asSchedulable {
716
+ return schedulable
717
+ }
718
+ if let taskExecutor = unsafe _getCurrentTaskExecutor( ) . asTaskExecutor ( ) ,
719
+ let schedulable = taskExecutor. asSchedulable {
720
+ return schedulable
721
+ }
722
+ return nil
723
+ }
660
724
}
661
725
662
726
0 commit comments