Skip to content

Commit 025b21a

Browse files
committed
bring back optional TaskExecutor param; introduce global var executor
1 parent 89b848e commit 025b21a

13 files changed

+271
-138
lines changed

stdlib/public/Concurrency/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -107,6 +107,7 @@ set(SWIFT_RUNTIME_CONCURRENCY_SWIFT_SOURCES
107107
AsyncThrowingMapSequence.swift
108108
AsyncThrowingPrefixWhileSequence.swift
109109
GlobalActor.swift
110+
GlobalConcurrentExecutor.swift
110111
MainActor.swift
111112
PartialAsyncTask.swift
112113
SourceCompatibilityShims.swift

stdlib/public/Concurrency/Executor.swift

Lines changed: 4 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -113,8 +113,8 @@ public protocol SerialExecutor: Executor {
113113
/// requirements.
114114
///
115115
/// By setting a task executor preference, either with a
116-
/// ``_withTaskExecutor(_:operation:)``, creating a task with a preference
117-
/// (`Task(_on:)`, or `group.addTask(on:)`), the task and all of its child
116+
/// ``_withTaskExecutorPreference(_:operation:)``, creating a task with a preference
117+
/// (`Task(_executorPreference:)`, or `group.addTask(executorPreference:)`), the task and all of its child
118118
/// tasks (unless a new preference is set) will be preferring to execute on
119119
/// the provided task executor.
120120
///
@@ -375,9 +375,8 @@ internal func _task_serialExecutor_getExecutorRef<E>(_ executor: E) -> Builtin.E
375375
@_unavailableInEmbedded
376376
@available(SwiftStdlib 9999, *)
377377
@_silgen_name("_task_executor_getTaskExecutorRef")
378-
internal func _task_executor_getTaskExecutorRef<E>(_ executor: E) -> Builtin.Executor
379-
where E: _TaskExecutor {
380-
return executor.asUnownedTaskExecutor().executor
378+
internal func _task_executor_getTaskExecutorRef(_ taskExecutor: any _TaskExecutor) -> Builtin.Executor {
379+
return taskExecutor.asUnownedTaskExecutor().executor
381380
}
382381

383382
// Used by the concurrency runtime
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
//===----------------------------------------------------------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2020 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
13+
import Swift
14+
@_implementationOnly import _SwiftConcurrencyShims
15+
16+
// None of _TaskExecutor APIs are available in task-to-thread concurrency model.
17+
#if !SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
18+
19+
/// The global concurrent executor that is used by default for Swift Concurrency
20+
/// tasks.
21+
///
22+
/// The executor's implementation is platform dependent.
23+
/// By default it uses a fixed size pool of threads and should not be used for
24+
/// blocking operations which do not guarantee forward progress as doing so may
25+
/// prevent other tasks from being executed and render the system unresponsive.
26+
///
27+
/// You may pass this executor explicitly to a ``Task`` initializer as a task
28+
/// executor preference, in order to ensure and document that task be executed
29+
/// on the global executor, instead e.g. inheriting the enclosing actor's
30+
/// executor. Refer to ``_withTaskExecutorPreference(_:operation:)`` for a
31+
/// detailed discussion of task executor preferences.
32+
///
33+
/// Customizing the global concurrent executor is currently not supported.
34+
@available(SwiftStdlib 9999, *)
35+
public var globalConcurrentExecutor: any _TaskExecutor {
36+
get {
37+
_DefaultGlobalConcurrentExecutor.shared
38+
}
39+
// TODO: introduce a set {} once we are ready to allow customizing the
40+
// default global executor. This should be done the same for main actor
41+
}
42+
43+
/// A task executor which enqueues all work on the default global concurrent
44+
/// thread pool that is used as the default executor for Swift concurrency
45+
/// tasks.
46+
@available(SwiftStdlib 9999, *)
47+
internal final class _DefaultGlobalConcurrentExecutor: _TaskExecutor {
48+
public static let shared: _DefaultGlobalConcurrentExecutor = .init()
49+
50+
private init() {}
51+
52+
public func enqueue(_ job: consuming ExecutorJob) {
53+
_enqueueJobGlobal(job.context)
54+
}
55+
56+
public func asUnownedTaskExecutor() {
57+
// The "default global concurrent executor" is simply the "undefined" one.
58+
// We represent it as the `(0, 0)` ExecutorRef and it is handled properly
59+
// by the runtime, without having to call through to the
60+
// `_DefaultGlobalConcurrentExecutor` declared in Swift.
61+
UnownedTaskExecutor(_getUndefinedTaskExecutor())
62+
}
63+
}
64+
65+
#endif

stdlib/public/Concurrency/PartialAsyncTask.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -121,7 +121,7 @@ public struct UnownedJob: Sendable {
121121
@available(SwiftStdlib 9999, *)
122122
@_alwaysEmitIntoClient
123123
@inlinable
124-
public func runSynchronously(isolated serialExecutor: UnownedSerialExecutor,
124+
public func runSynchronously(isolatedTo serialExecutor: UnownedSerialExecutor,
125125
taskExecutor: UnownedTaskExecutor) {
126126
_swiftJobRunOnTaskExecutor(self, serialExecutor, taskExecutor)
127127
}

stdlib/public/Concurrency/Task+TaskExecutor.swift

Lines changed: 90 additions & 60 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,50 @@ import Swift
2929
/// Passing `nil` as executor means disabling any preference preference (if it was set) and the task hierarchy
3030
/// will execute without any executor preference until a different preference is set.
3131
///
32+
/// ### Asynchronous function execution semantics in presence of task executor preferences
33+
/// The following diagram illustrates on which executor an `async` function will
34+
/// execute, in presence (or lack thereof) a task executor preference.
35+
///
36+
/// ```
37+
/// [ func / closure ] - /* where should it execute? */
38+
/// |
39+
/// +--------------+ +===========================+
40+
/// +-------- | is isolated? | - yes -> | actor has unownedExecutor |
41+
/// | +--------------+ +===========================+
42+
/// | | |
43+
/// | yes no
44+
/// | | |
45+
/// | v v
46+
/// | +=======================+ /* task executor preference? */
47+
/// | | on specified executor | | |
48+
/// | +=======================+ yes no
49+
/// | | |
50+
/// | | v
51+
/// | | +==========================+
52+
/// | | | default (actor) executor |
53+
/// | v +==========================+
54+
/// v +==============================+
55+
/// /* task executor preference? */ ---- yes ----> | on Task's preferred executor |
56+
/// | +==============================+
57+
/// no
58+
/// |
59+
/// v
60+
/// +===============================+
61+
/// | on global concurrent executor |
62+
/// +===============================+
63+
/// ```
64+
///
65+
/// In short, without a task executor preference, `nonisolated async` functions
66+
/// will execute on the global concurrent executor. If a task executor preference
67+
/// is present, such `nonisolated async` functions will execute on the preferred
68+
/// task executor.
69+
///
70+
/// Isolated functions semantically execute on the actor they are isolated to,
71+
/// however if such actor does not declare a custom executor (it is a "default
72+
/// actor") in presence of a task executor preference, tasks executing on this
73+
/// actor will use the preferred executor as source of threads to run the task,
74+
/// while isolated on the actor.
75+
///
3276
/// ### Example
3377
///
3478
/// Task {
@@ -55,7 +99,7 @@ import Swift
5599
/// async let x = ...
56100
/// await withTaskGroup(of: Int.self) { group in
57101
/// group.addTask { 7 } // child task executes on 'specific' executor
58-
/// group.addTask(on: .default) { 13 } // child task executes on default executor
102+
/// group.addTask(executorPreference: globalConcurrentExecutor) { 13 } // child task executes on default executor
59103
/// }
60104
///
61105
/// // disable the task executor preference:
@@ -75,20 +119,24 @@ import Swift
75119
/// }
76120
///
77121
/// - Parameters:
78-
/// - executorPreference: the task executor to use as preferred task executor; if `nil` it is interpreted as "no preference"
122+
/// - preferredTaskExecutor: the task executor to use as preferred task executor; if `nil` it is interpreted as "no preference"
79123
/// - operation: the operation to execute on the passed executor; if the executor was `nil`, this will execute on the default global concurrent executor.
80124
/// - Returns: the value returned from the `operation` closure
81125
/// - Throws: if the operation closure throws
82126
/// - SeeAlso: `_TaskExecutor`
83127
@_unavailableInEmbedded
84128
@available(SwiftStdlib 9999, *)
85129
@_unsafeInheritExecutor // calling withTaskExecutor MUST NOT perform the "usual" hop to global
86-
public func _withTaskExecutor<T: Sendable>(
87-
_ taskExecutorPreference: some _TaskExecutor,
130+
public func _withTaskExecutorPreference<T: Sendable>(
131+
_ taskExecutor: (any _TaskExecutor)?,
88132
operation: @Sendable () async throws -> T
89133
) async rethrows -> T {
90134
let taskExecutorBuiltin: Builtin.Executor =
91-
taskExecutorPreference.asUnownedTaskExecutor().executor
135+
if let taskExecutor {
136+
taskExecutor.asUnownedTaskExecutor().executor
137+
} else {
138+
globalConcurrentExecutor.asUnownedTaskExecutor().executor
139+
}
92140

93141
let record = _pushTaskExecutorPreference(taskExecutorBuiltin)
94142
defer {
@@ -101,44 +149,6 @@ public func _withTaskExecutor<T: Sendable>(
101149
return try await operation()
102150
}
103151

104-
/// Default global concurrent pool TaskExecutor --------------------------------
105-
106-
@available(SwiftStdlib 9999, *)
107-
extension _TaskExecutor where Self == DefaultConcurrentExecutor {
108-
109-
/// The default executor preference, setting it using ``withTaskExecutor(_:)``
110-
/// is equivalent to disabling an existing task executor preference, or
111-
/// stating that task now has "no task executor preference".
112-
@available(SwiftStdlib 9999, *)
113-
public static var `default`: DefaultConcurrentExecutor {
114-
DefaultConcurrentExecutor.shared
115-
}
116-
117-
}
118-
119-
/// A task executor which enqueues all work on the default global concurrent
120-
/// thread pool that is used as the default executor for Swift concurrency
121-
/// tasks.
122-
@available(SwiftStdlib 9999, *)
123-
public final class DefaultConcurrentExecutor: _TaskExecutor {
124-
public static let shared: DefaultConcurrentExecutor = .init()
125-
126-
private init() {}
127-
128-
public func enqueue(_ job: consuming ExecutorJob) {
129-
_enqueueJobGlobal(job.context)
130-
}
131-
132-
public func asUnownedTaskExecutor() {
133-
// The "default global concurrent executor" is simply the "undefined" one.
134-
// We represent it as the `(0, 0)` ExecutorRef and it is handled properly
135-
// by the runtime, without having to call through to the
136-
// `DefaultConcurrentExecutor` declared in Swift.
137-
UnownedTaskExecutor(_getUndefinedTaskExecutor())
138-
}
139-
140-
}
141-
142152
/// Task with specified executor -----------------------------------------------
143153

144154
@available(SwiftStdlib 9999, *)
@@ -150,7 +160,7 @@ extension Task where Failure == Never {
150160
/// This overload allows specifying a preferred ``_TaskExecutor`` on which
151161
/// the `operation`, as well as all child tasks created from this task will be
152162
/// executing whenever possible. Refer to ``_TaskExecutor`` for a detailed discussion
153-
// of the effect of task executors on execution semantics of asynchronous code.
163+
/// of the effect of task executors on execution semantics of asynchronous code.
154164
///
155165
/// Use this function when creating asynchronous work
156166
/// that operates on behalf of the synchronous function that calls it.
@@ -169,14 +179,14 @@ extension Task where Failure == Never {
169179
/// it only makes it impossible for you to explicitly cancel the task.
170180
///
171181
/// - Parameters:
172-
/// - executor: the preferred task executor for this task, and any child tasks created by it
182+
/// - taskExecutor: the preferred task executor for this task, and any child tasks created by it
173183
/// - priority: The priority of the task.
174184
/// Pass `nil` to use the priority from `Task.currentPriority`.
175185
/// - operation: The operation to perform.
176186
@discardableResult
177187
@_alwaysEmitIntoClient
178188
public init(
179-
_on executor: some _TaskExecutor,
189+
_executorPreference taskExecutor: (any _TaskExecutor)?,
180190
priority: TaskPriority? = nil,
181191
operation: __owned @Sendable @escaping () async -> Success
182192
) {
@@ -189,9 +199,15 @@ extension Task where Failure == Never {
189199
isDiscardingTask: false)
190200

191201
// Create the asynchronous task.
192-
let taskExecutorRef = executor.asUnownedTaskExecutor()
202+
let executorBuiltin: Builtin.Executor =
203+
if let taskExecutor {
204+
taskExecutor.asUnownedTaskExecutor().executor
205+
} else {
206+
globalConcurrentExecutor.asUnownedTaskExecutor().executor
207+
}
208+
193209
let (task, _) = Builtin.createAsyncTaskWithExecutor(
194-
flags, taskExecutorRef.executor, operation)
210+
flags, executorBuiltin, operation)
195211
self._task = task
196212
#else
197213
fatalError("Unsupported Swift compiler, missing support for BuiltinCreateAsyncTaskWithExecutor")
@@ -204,7 +220,7 @@ extension Task where Failure == Error {
204220
@discardableResult
205221
@_alwaysEmitIntoClient
206222
public init(
207-
_on executor: some _TaskExecutor,
223+
_executorPreference taskExecutor: (any _TaskExecutor)?,
208224
priority: TaskPriority? = nil,
209225
operation: __owned @Sendable @escaping () async throws -> Success
210226
) {
@@ -217,9 +233,14 @@ extension Task where Failure == Error {
217233
isDiscardingTask: false)
218234

219235
// Create the asynchronous task.
220-
let taskExecutorRef = executor.asUnownedTaskExecutor()
236+
let executorBuiltin: Builtin.Executor =
237+
if let taskExecutor {
238+
taskExecutor.asUnownedTaskExecutor().executor
239+
} else {
240+
globalConcurrentExecutor.asUnownedTaskExecutor().executor
241+
}
221242
let (task, _) = Builtin.createAsyncTaskWithExecutor(
222-
flags, taskExecutorRef.executor, operation)
243+
flags, executorBuiltin, operation)
223244
self._task = task
224245
#else
225246
fatalError("Unsupported Swift compiler, missing support for $BuiltinCreateAsyncTaskWithExecutor")
@@ -236,7 +257,7 @@ extension Task where Failure == Never {
236257
@_alwaysEmitIntoClient
237258
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model")
238259
public static func _detached(
239-
on executor: some _TaskExecutor,
260+
_executorPreference taskExecutor: (any _TaskExecutor)?,
240261
priority: TaskPriority? = nil,
241262
operation: __owned @Sendable @escaping () async -> Success
242263
) -> Task<Success, Failure> {
@@ -267,7 +288,7 @@ extension Task where Failure == Never {
267288
@discardableResult
268289
@_alwaysEmitIntoClient
269290
public static func _detached(
270-
on executor: some _TaskExecutor,
291+
_executorPreference taskExecutor: (any _TaskExecutor)?,
271292
priority: TaskPriority? = nil,
272293
operation: __owned @Sendable @escaping () async -> Success
273294
) -> Task<Success, Failure> {
@@ -280,9 +301,14 @@ extension Task where Failure == Never {
280301
isDiscardingTask: false)
281302

282303
// Create the asynchronous task.
283-
let taskExecutorRef = executor.asUnownedTaskExecutor()
304+
let executorBuiltin: Builtin.Executor =
305+
if let taskExecutor {
306+
taskExecutor.asUnownedTaskExecutor().executor
307+
} else {
308+
globalConcurrentExecutor.asUnownedTaskExecutor().executor
309+
}
284310
let (task, _) = Builtin.createAsyncTaskWithExecutor(
285-
flags, taskExecutorRef.executor, operation)
311+
flags, executorBuiltin, operation)
286312

287313
return Task(task)
288314
#else
@@ -299,7 +325,7 @@ extension Task where Failure == Error {
299325
@_alwaysEmitIntoClient
300326
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model")
301327
public static func _detached(
302-
on executor: some _TaskExecutor,
328+
_executorPreference taskExecutor: (any _TaskExecutor)?,
303329
priority: TaskPriority? = nil,
304330
operation: __owned @Sendable @escaping () async throws -> Success
305331
) -> Task<Success, Failure> {
@@ -332,7 +358,7 @@ extension Task where Failure == Error {
332358
@discardableResult
333359
@_alwaysEmitIntoClient
334360
public static func _detached(
335-
on executor: some _TaskExecutor,
361+
_executorPreference taskExecutor: (any _TaskExecutor)?,
336362
priority: TaskPriority? = nil,
337363
operation: __owned @Sendable @escaping () async throws -> Success
338364
) -> Task<Success, Failure> {
@@ -345,10 +371,14 @@ extension Task where Failure == Error {
345371
isDiscardingTask: false)
346372

347373
// Create the asynchronous task.
348-
// Create the asynchronous task.
349-
let taskExecutorRef = executor.asUnownedTaskExecutor()
374+
let executorBuiltin: Builtin.Executor =
375+
if let taskExecutor {
376+
taskExecutor.asUnownedTaskExecutor().executor
377+
} else {
378+
globalConcurrentExecutor.asUnownedTaskExecutor().executor
379+
}
350380
let (task, _) = Builtin.createAsyncTaskWithExecutor(
351-
flags, taskExecutorRef.executor, operation)
381+
flags, executorBuiltin, operation)
352382

353383
return Task(task)
354384
#else

0 commit comments

Comments
 (0)