Skip to content

Commit 444bbd5

Browse files
committed
[Concurrency] Update following pitch comments.
Remove `supportsScheduling` in favour of a type-based approach. Update the storage for `ClockTraits` to `UInt32`. Adjust ordering of executors for `currentExecutor`. rdar://141348916
1 parent 23d0ca7 commit 444bbd5

File tree

12 files changed

+149
-97
lines changed

12 files changed

+149
-97
lines changed

stdlib/public/Concurrency/Clock.swift

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -200,9 +200,9 @@ extension Clock {
200200
/// basis.
201201
@available(SwiftStdlib 6.2, *)
202202
public struct ClockTraits: OptionSet {
203-
public let rawValue: Int32
203+
public let rawValue: UInt32
204204

205-
public init(rawValue: Int32) {
205+
public init(rawValue: UInt32) {
206206
self.rawValue = rawValue
207207
}
208208

stdlib/public/Concurrency/DispatchExecutor.swift

Lines changed: 12 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ import Swift
2525

2626
@available(SwiftStdlib 6.2, *)
2727
public class DispatchMainExecutor: RunLoopExecutor, @unchecked Sendable {
28+
public typealias AsSchedulable = DispatchMainExecutor
29+
2830
var threaded = false
2931

3032
public init() {}
@@ -52,8 +54,13 @@ extension DispatchMainExecutor: SerialExecutor {
5254

5355
public var isMainExecutor: Bool { true }
5456

55-
public var supportsScheduling: Bool { true }
57+
public func checkIsolated() {
58+
_dispatchAssertMainQueue()
59+
}
60+
}
5661

62+
@available(SwiftStdlib 6.2, *)
63+
extension DispatchMainExecutor: SchedulableExecutor {
5764
public func enqueue<C: Clock>(_ job: consuming ExecutorJob,
5865
at instant: C.Instant,
5966
tolerance: C.Duration? = nil,
@@ -74,10 +81,6 @@ extension DispatchMainExecutor: SerialExecutor {
7481
clockID.rawValue,
7582
UnownedJob(job))
7683
}
77-
78-
public func checkIsolated() {
79-
_dispatchAssertMainQueue()
80-
}
8184
}
8285

8386
@available(SwiftStdlib 6.2, *)
@@ -116,7 +119,10 @@ extension DispatchMainExecutor: MainExecutor {}
116119
// .. Task Executor ............................................................
117120

118121
@available(SwiftStdlib 6.2, *)
119-
public class DispatchGlobalTaskExecutor: TaskExecutor, @unchecked Sendable {
122+
public class DispatchGlobalTaskExecutor: TaskExecutor, SchedulableExecutor,
123+
@unchecked Sendable {
124+
125+
public typealias AsSchedulable = DispatchGlobalTaskExecutor
120126

121127
public init() {}
122128

@@ -126,8 +132,6 @@ public class DispatchGlobalTaskExecutor: TaskExecutor, @unchecked Sendable {
126132

127133
public var isMainExecutor: Bool { false }
128134

129-
public var supportsScheduling: Bool { true }
130-
131135
public func enqueue<C: Clock>(_ job: consuming ExecutorJob,
132136
at instant: C.Instant,
133137
tolerance: C.Duration? = nil,

stdlib/public/Concurrency/Executor.swift

Lines changed: 95 additions & 31 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,13 @@ import Swift
1616
@available(SwiftStdlib 5.1, *)
1717
public protocol Executor: AnyObject, Sendable {
1818

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+
1926
// Since lack move-only type support in the SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY configuration
2027
// Do not deprecate the UnownedJob enqueue in that configuration just yet - as we cannot introduce the replacements.
2128
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
@@ -46,16 +53,25 @@ public protocol Executor: AnyObject, Sendable {
4653
@available(SwiftStdlib 6.2, *)
4754
var isMainExecutor: Bool { get }
4855
#endif
56+
}
4957

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 {
5975

6076
#if !$Embedded
6177

@@ -101,6 +117,24 @@ public protocol Executor: AnyObject, Sendable {
101117
clock: C)
102118

103119
#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+
}
104138
}
105139

106140
extension Executor {
@@ -115,7 +149,6 @@ extension Executor where Self: Equatable {
115149
internal var _isComplexEquality: Bool { true }
116150
}
117151

118-
// Delay support
119152
extension Executor {
120153

121154
#if !$Embedded
@@ -125,10 +158,11 @@ extension Executor {
125158
public var isMainExecutor: Bool { false }
126159
#endif
127160

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 {
132166

133167
#if !$Embedded
134168

@@ -137,10 +171,6 @@ extension Executor {
137171
after delay: C.Duration,
138172
tolerance: C.Duration? = nil,
139173
clock: C) {
140-
if !supportsScheduling {
141-
fatalError("Executor \(self) does not support scheduling")
142-
}
143-
144174
// If you crash here with a mutual recursion, it's because you didn't
145175
// implement one of these two functions
146176
enqueue(job, at: clock.now.advanced(by: delay),
@@ -152,10 +182,6 @@ extension Executor {
152182
at instant: C.Instant,
153183
tolerance: C.Duration? = nil,
154184
clock: C) {
155-
if !supportsScheduling {
156-
fatalError("Executor \(self) does not support scheduling")
157-
}
158-
159185
// If you crash here with a mutual recursion, it's because you didn't
160186
// implement one of these two functions
161187
enqueue(job, after: clock.now.duration(to: instant),
@@ -493,9 +519,9 @@ public protocol RunLoopExecutor: Executor {
493519
///
494520
/// Parameters:
495521
///
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
499525

500526
/// Signal to the run loop to stop running and return.
501527
///
@@ -517,9 +543,7 @@ extension RunLoopExecutor {
517543
}
518544

519545

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`.
523547
@available(SwiftStdlib 6.2, *)
524548
public struct ExecutorEvent: Identifiable, Comparable, Sendable {
525549
public typealias ID = Int
@@ -645,18 +669,58 @@ extension Task where Success == Never, Failure == Never {
645669
extension Task where Success == Never, Failure == Never {
646670
/// Get the current executor; this is the executor that the currently
647671
/// 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`.
648681
@available(SwiftStdlib 6.2, *)
649682
@_unavailableInEmbedded
650683
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() {
654685
return activeExecutor
686+
} else if let taskExecutor = unsafe _getPreferredTaskExecutor().asTaskExecutor() {
687+
return taskExecutor
655688
} else if let taskExecutor = unsafe _getCurrentTaskExecutor().asTaskExecutor() {
656689
return taskExecutor
657690
}
658691
return nil
659692
}
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+
}
660724
}
661725

662726

stdlib/public/Concurrency/ExecutorImpl.swift

Lines changed: 12 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,7 @@ internal func dontateToGlobalExecutor(
3434
context: UnsafeMutableRawPointer
3535
) {
3636
if let runnableExecutor = Task.defaultExecutor as? RunLoopExecutor {
37-
try! runnableExecutor.run(until: { unsafe Bool(condition(context)) })
37+
try! runnableExecutor.runUntil { unsafe Bool(condition(context)) }
3838
} else {
3939
fatalError("Global executor does not support thread donation")
4040
}
@@ -63,9 +63,9 @@ internal func enqueueOnGlobalExecutor(job unownedJob: UnownedJob) {
6363
@_silgen_name("swift_task_enqueueGlobalWithDelayImpl")
6464
internal func enqueueOnGlobalExecutor(delay: CUnsignedLongLong,
6565
job unownedJob: UnownedJob) {
66-
Task.defaultExecutor.enqueue(ExecutorJob(unownedJob),
67-
after: .nanoseconds(delay),
68-
clock: .continuous)
66+
Task.defaultExecutor.asSchedulable!.enqueue(ExecutorJob(unownedJob),
67+
after: .nanoseconds(delay),
68+
clock: .continuous)
6969
}
7070

7171
@available(SwiftStdlib 6.2, *)
@@ -80,15 +80,15 @@ internal func enqueueOnGlobalExecutor(seconds: CLongLong,
8080
let leeway = Duration.seconds(leewaySeconds) + Duration.nanoseconds(leewayNanoseconds)
8181
switch clock {
8282
case _ClockID.suspending.rawValue:
83-
Task.defaultExecutor.enqueue(ExecutorJob(unownedJob),
84-
after: delay,
85-
tolerance: leeway,
86-
clock: .suspending)
83+
Task.defaultExecutor.asSchedulable!.enqueue(ExecutorJob(unownedJob),
84+
after: delay,
85+
tolerance: leeway,
86+
clock: .suspending)
8787
case _ClockID.continuous.rawValue:
88-
Task.defaultExecutor.enqueue(ExecutorJob(unownedJob),
89-
after: delay,
90-
tolerance: leeway,
91-
clock: .continuous)
88+
Task.defaultExecutor.asSchedulable!.enqueue(ExecutorJob(unownedJob),
89+
after: delay,
90+
tolerance: leeway,
91+
clock: .continuous)
9292
default:
9393
fatalError("Unknown clock ID \(clock)")
9494
}

stdlib/public/Concurrency/PlatformExecutorEmbedded.swift

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,6 @@ public final class EmbeddedMainExecutor: MainExecutor, @unchecked Sendable {
3131
// We can't implement enqueue<C: Clock> in Embedde Swift because we aren't
3232
// allowed to have generics in an existential there.
3333

34-
public var supportsScheduling: Bool { false }
35-
3634
public func run() throws {
3735
}
3836

@@ -64,9 +62,4 @@ public final class EmbeddedDefaultExecutor: TaskExecutor, @unchecked Sendable {
6462
public func enqueue(_ job: consuming ExecutorJob) {
6563
}
6664

67-
// We can't implement enqueue<C: Clock> in Embedde Swift because we aren't
68-
// allowed to have generics in an existential there.
69-
70-
public var supportsScheduling: Bool { false }
71-
7265
}

stdlib/public/Concurrency/Task.swift

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1253,7 +1253,18 @@ extension Task where Success == Never, Failure == Never {
12531253
let job = _taskCreateNullaryContinuationJob(
12541254
priority: Int(Task.currentPriority.rawValue),
12551255
continuation: continuation)
1256+
1257+
#if !$Embedded
1258+
if #available(SwiftStdlib 6.2, *) {
1259+
let executor = Task.currentExecutor ?? Task.defaultExecutor
1260+
1261+
executor.enqueue(ExecutorJob(context: job))
1262+
} else {
1263+
_enqueueJobGlobal(job)
1264+
}
1265+
#else
12561266
_enqueueJobGlobal(job)
1267+
#endif
12571268
}
12581269
}
12591270
}

stdlib/public/Concurrency/TaskSleep.swift

Lines changed: 10 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,12 +29,13 @@ extension Task where Success == Never, Failure == Never {
2929
continuation: continuation)
3030

3131
if #available(SwiftStdlib 6.2, *) {
32-
let executor = Task.currentExecutor ?? Task.defaultExecutor
32+
let executor = Task.currentSchedulableExecutor
33+
?? Task.defaultExecutor.asSchdulable
3334

3435
#if !$Embedded
35-
executor.enqueue(ExecutorJob(context: job),
36-
after: .nanoseconds(duration),
37-
clock: .continuous)
36+
executor!.enqueue(ExecutorJob(context: job),
37+
after: .nanoseconds(duration),
38+
clock: .continuous)
3839
#endif
3940
} else {
4041
// Since we're building the new version of the stdlib, we should
@@ -270,12 +271,13 @@ extension Task where Success == Never, Failure == Never {
270271
}
271272

272273
if #available(SwiftStdlib 6.2, *) {
273-
let executor = Task.currentExecutor ?? Task.defaultExecutor
274+
let executor = Task.currentSchedulableExecutor
275+
?? Task.defaultExecutor.asSchedulable
274276
let job = ExecutorJob(context: Builtin.convertTaskToJob(sleepTask))
275277
#if !$Embedded
276-
executor.enqueue(job,
277-
after: .nanoseconds(duration),
278-
clock: .continuous)
278+
executor!.enqueue(job,
279+
after: .nanoseconds(duration),
280+
clock: .continuous)
279281
#endif
280282
} else {
281283
// Shouldn't be able to get here

0 commit comments

Comments
 (0)