Skip to content

Commit 47db5ad

Browse files
authored
[Concurrency] Align Task.isolated with proposal (add task executor) (#82913)
2 parents 6b577db + 664be9b commit 47db5ad

File tree

7 files changed

+321
-47
lines changed

7 files changed

+321
-47
lines changed

stdlib/public/Concurrency/Task+immediate.swift.gyb

Lines changed: 193 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -13,15 +13,15 @@
1313
import Swift
1414
@_implementationOnly import SwiftConcurrencyInternalShims
1515

16-
// ==== Task.immediate ---------------------------------------------------------
16+
// ==== Task.startSynchronously ------------------------------------------------
1717

1818
% METHOD_VARIANTS = [
1919
% 'THROWING',
2020
% 'NON_THROWING',
2121
% ]
22-
% for METHOD_VARIANT in METHOD_VARIANTS:
22+
% for THROWING_VARIANT in METHOD_VARIANTS:
2323

24-
% IS_THROWING = METHOD_VARIANT == 'THROWING'
24+
% IS_THROWING = THROWING_VARIANT == 'THROWING'
2525
% if IS_THROWING:
2626
% FAILURE_TYPE = 'Error'
2727
% THROWS = 'throws '
@@ -51,8 +51,38 @@ extension Task where Failure == ${FAILURE_TYPE} {
5151
) -> Task<Success, ${FAILURE_TYPE}> {
5252
immediate(name: name, priority: priority, operation: operation)
5353
}
54+
}
55+
56+
% end
57+
58+
// ==== Task.immediate(Detached) ---------------------------------------------------------
59+
60+
% METHOD_VARIANTS = [
61+
% ('immediate', 'THROWING'),
62+
% ('immediate', 'NON_THROWING'),
63+
% ('immediateDetached', 'THROWING'),
64+
% ('immediateDetached', 'NON_THROWING'),
65+
% ]
66+
% for (METHOD_NAME, THROWING_VARIANT) in METHOD_VARIANTS:
67+
68+
% IS_THROWING = THROWING_VARIANT == 'THROWING'
69+
% IS_DETACHED = 'Detached' in METHOD_NAME
70+
% if IS_THROWING:
71+
% FAILURE_TYPE = 'Error'
72+
% THROWS = 'throws '
73+
% else:
74+
% FAILURE_TYPE = 'Never'
75+
% THROWS = ''
76+
% end
5477

78+
@available(SwiftStdlib 6.2, *)
79+
extension Task where Failure == ${FAILURE_TYPE} {
80+
81+
% if IS_DETACHED:
5582
/// Create and immediately start running a new task in the context of the calling thread/task.
83+
% else:
84+
/// Create and immediately start running a new detached task in the context of the calling thread/task.
85+
% end # IS_DETACHED
5686
///
5787
/// This function _starts_ the created task on the calling context.
5888
/// The task will continue executing on the caller's context until it suspends,
@@ -65,21 +95,30 @@ extension Task where Failure == ${FAILURE_TYPE} {
6595
/// a synchronous manner.
6696
///
6797
/// Other than the execution semantics discussed above, the created task
68-
/// is semantically equivalent to its basic version which can be
69-
/// created using ``Task/init``.
98+
/// is semantically equivalent to a task created using
99+
% if IS_DETACHED:
100+
/// the ``Task/detached`` function.
101+
% else:
102+
/// the ``Task/init`` initializer.
103+
% end
70104
///
71105
/// - Parameters:
72106
/// - name: The high-level human-readable name given for this task
73107
/// - priority: The priority of the task.
74108
/// Pass `nil` to use the ``Task/basePriority`` of the current task (if there is one).
109+
/// - taskExecutor: The task executor that the child task should be started on and keep using.
110+
/// Explicitly passing `nil` as the executor preference is equivalent to no preference,
111+
/// and effectively means to inherit the outer context's executor preference.
112+
/// You can also pass the ``globalConcurrentExecutor`` global executor explicitly.
75113
/// - operation: the operation to be run immediately upon entering the task.
76114
/// - Returns: A reference to the unstructured task which may be awaited on.
77115
@available(SwiftStdlib 6.2, *)
78116
@_alwaysEmitIntoClient
79117
@discardableResult
80-
public static func immediate(
118+
public static func ${METHOD_NAME}(
81119
name: String? = nil,
82120
priority: TaskPriority? = nil,
121+
executorPreference taskExecutor: consuming (any TaskExecutor)? = nil,
83122
@_implicitSelfCapture @_inheritActorContext(always) operation: sending @isolated(any) @escaping () async ${THROWS} -> Success
84123
) -> Task<Success, ${FAILURE_TYPE}> {
85124

@@ -93,14 +132,14 @@ extension Task where Failure == ${FAILURE_TYPE} {
93132
if let builtinSerialExecutor {
94133
_taskIsCurrentExecutor(executor: builtinSerialExecutor, flags: flagsMustNotCrash)
95134
} else {
96-
true // if there is not target executor, we can run synchronously
135+
true // if there is no target executor, we can run synchronously
97136
}
98137

99138
let flags = taskCreateFlags(
100139
priority: priority,
101140
isChildTask: false,
102-
copyTaskLocals: true,
103-
inheritContext: true,
141+
copyTaskLocals: ${'true' if not IS_DETACHED else 'false /* detached */'},
142+
inheritContext: ${'true' if not IS_DETACHED else 'false /* detached */'},
104143
enqueueJob: !canRunSynchronously,
105144
addPendingGroupTaskUnconditionally: false,
106145
isDiscardingTask: false,
@@ -110,6 +149,17 @@ extension Task where Failure == ${FAILURE_TYPE} {
110149
var task: Builtin.NativeObject?
111150
#if $BuiltinCreateAsyncTaskName
112151
if let name {
152+
#if $BuiltinCreateAsyncTaskOwnedTaskExecutor
153+
task =
154+
unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
155+
Builtin.createTask(
156+
flags: flags,
157+
initialSerialExecutor: builtinSerialExecutor,
158+
initialTaskExecutorConsuming: taskExecutor,
159+
taskName: nameBytes.baseAddress!._rawValue,
160+
operation: operation).0
161+
}
162+
#else // no $BuiltinCreateAsyncTaskOwnedTaskExecutor
113163
task =
114164
unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
115165
Builtin.createTask(
@@ -118,12 +168,37 @@ extension Task where Failure == ${FAILURE_TYPE} {
118168
taskName: nameBytes.baseAddress!._rawValue,
119169
operation: operation).0
120170
}
171+
#endif // $BuiltinCreateAsyncTaskOwnedTaskExecutor
172+
} // let name
173+
#endif // $BuiltinCreateAsyncTaskName
174+
175+
// Task name was not set, or task name createTask is unavailable
176+
if task == nil {
177+
assert(name == nil)
178+
#if $BuiltinCreateAsyncTaskOwnedTaskExecutor
179+
task = Builtin.createTask(
180+
flags: flags,
181+
initialSerialExecutor: builtinSerialExecutor,
182+
initialTaskExecutorConsuming: taskExecutor,
183+
operation: operation).0
184+
#else
185+
// legacy branch for the non-consuming task executor
186+
let executorBuiltin: Builtin.Executor =
187+
taskExecutor.asUnownedTaskExecutor().executor
188+
189+
task = Builtin.createTask(
190+
flags: flags,
191+
initialSerialExecutor: builtinSerialExecutor,
192+
initialTaskExecutor: executorBuiltin,
193+
operation: operation).0
194+
#endif
121195
}
122-
#endif
196+
123197
if task == nil {
124198
// either no task name was set, or names are unsupported
125199
task = Builtin.createTask(
126200
flags: flags,
201+
initialSerialExecutor: builtinSerialExecutor,
127202
operation: operation).0
128203
}
129204

@@ -177,10 +252,22 @@ GROUP_AND_OP_INFO = [
177252
}%
178253
% for (GROUP_TYPE, METHOD_NAMES, THROWS, RESULT_TYPE) in GROUP_AND_OP_INFO:
179254
% for METHOD_NAME in METHOD_NAMES:
255+
%
256+
% IS_DISCARDING = 'Discarding' in GROUP_TYPE
257+
% IS_ADD_UNLESS_CANCELLED = METHOD_NAME == "addImmediateTaskUnlessCancelled"
258+
%
259+
% ARROW_RETURN_TYPE = "-> Bool " if IS_ADD_UNLESS_CANCELLED else ""
260+
%
261+
% if IS_DISCARDING:
262+
% TASK_CREATE_FN = 'Builtin.createDiscardingTask'
263+
% else:
264+
% TASK_CREATE_FN = 'Builtin.createTask'
265+
% end
266+
180267
@available(SwiftStdlib 6.2, *)
181268
extension ${GROUP_TYPE} {
182269

183-
/// Create and immediately start running a new child task in the context of the calling thread/task.
270+
/// Add a child task to the group and immediately start running it in the context of the calling thread/task.
184271
///
185272
/// This function _starts_ the created task on the calling context.
186273
/// The task will continue executing on the caller's context until it suspends,
@@ -195,36 +282,116 @@ extension ${GROUP_TYPE} {
195282
/// Other than the execution semantics discussed above, the created task
196283
/// is semantically equivalent to its basic version which can be
197284
/// created using ``${GROUP_TYPE}/addTask``.
285+
///
286+
/// - Parameters:
287+
/// - name: Human readable name of this task.
288+
/// - priority: The priority of the operation task.
289+
/// Omit this parameter or pass `nil` to inherit the task group's base priority.
290+
/// - taskExecutor: The task executor that the child task should be started on and keep using.
291+
/// Explicitly passing `nil` as the executor preference is equivalent to
292+
/// calling the `${METHOD_NAME}` method without a preference, and effectively
293+
/// means to inherit the outer context's executor preference.
294+
/// You can also pass the ``globalConcurrentExecutor`` global executor explicitly.
295+
/// - operation: The operation to execute as part of the task group.
296+
% if IS_ADD_UNLESS_CANCELLED:
297+
/// - Returns: `true` if the child task was added to the group;
298+
/// otherwise `false`.
299+
% end
198300
@available(SwiftStdlib 6.2, *)
199301
@_alwaysEmitIntoClient
200-
public func ${METHOD_NAME}( // in ${GROUP_TYPE}
302+
public mutating func ${METHOD_NAME}( // in ${GROUP_TYPE}
201303
name: String? = nil,
202304
priority: TaskPriority? = nil,
305+
executorPreference taskExecutor: consuming (any TaskExecutor)? = nil,
203306
@_inheritActorContext @_implicitSelfCapture operation: sending @isolated(any) @escaping () async ${THROWS}-> ${RESULT_TYPE}
204-
) {
307+
) ${ARROW_RETURN_TYPE}{
308+
309+
% if IS_ADD_UNLESS_CANCELLED:
310+
let canAdd = _taskGroupAddPendingTask(group: _group, unconditionally: false)
311+
312+
guard canAdd else {
313+
// the group is cancelled and is not accepting any new work
314+
return false
315+
}
316+
% end # IS_ADD_UNLESS_CANCELLED
317+
205318
let flags = taskCreateFlags(
206319
priority: priority,
207320
isChildTask: true,
208321
copyTaskLocals: false,
209322
inheritContext: false,
210323
enqueueJob: false, // don't enqueue, we'll run it manually
324+
% if IS_ADD_UNLESS_CANCELLED:
325+
% # In this case, we already added the pending task count before we create the task
326+
% # so we must not add to the pending counter again.
327+
addPendingGroupTaskUnconditionally: false,
328+
% else:
211329
addPendingGroupTaskUnconditionally: true,
212-
isDiscardingTask: ${'true' if 'Discarding' in GROUP_TYPE else 'false'},
330+
% end
331+
isDiscardingTask: ${str(IS_DISCARDING).lower()},
213332
isSynchronousStart: true
214333
)
215334

216-
// Create the asynchronous task.
217335
let builtinSerialExecutor =
218336
unsafe Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
219337

220-
// Create the task in this group.
221-
let (task, _) = Builtin.createTask(
222-
flags: flags,
223-
initialSerialExecutor: builtinSerialExecutor,
224-
taskGroup: self._group,
225-
operation: operation
226-
)
227-
_startTaskImmediately(task, targetExecutor: builtinSerialExecutor)
338+
var task: Builtin.NativeObject?
339+
340+
#if $BuiltinCreateAsyncTaskName
341+
if let name {
342+
task =
343+
unsafe name.utf8CString.withUnsafeBufferPointer { nameBytes in
344+
${TASK_CREATE_FN}(
345+
flags: flags,
346+
initialSerialExecutor: builtinSerialExecutor,
347+
taskGroup: _group,
348+
initialTaskExecutorConsuming: taskExecutor,
349+
taskName: nameBytes.baseAddress!._rawValue,
350+
operation: operation).0
351+
}
352+
}
353+
#endif // $BuiltinCreateAsyncTaskName
354+
355+
// Task name was not set, or task name createTask is unavailable
356+
if task == nil, let taskExecutor {
357+
#if $BuiltinCreateAsyncTaskOwnedTaskExecutor
358+
task = ${TASK_CREATE_FN}(
359+
flags: flags,
360+
initialSerialExecutor: builtinSerialExecutor,
361+
taskGroup: _group,
362+
initialTaskExecutorConsuming: taskExecutor,
363+
operation: operation).0
364+
#else
365+
// legacy branch for the non-consuming task executor
366+
let executorBuiltin: Builtin.Executor =
367+
taskExecutor.asUnownedTaskExecutor().executor
368+
369+
task = ${TASK_CREATE_FN}(
370+
flags: flags,
371+
initialSerialExecutor: builtinSerialExecutor,
372+
taskGroup: _group,
373+
initialTaskExecutor: executorBuiltin,
374+
operation: operation).0
375+
#endif
376+
}
377+
378+
if task == nil {
379+
task = ${TASK_CREATE_FN}(
380+
flags: flags,
381+
initialSerialExecutor: builtinSerialExecutor,
382+
taskGroup: _group,
383+
operation: operation).0
384+
}
385+
386+
// Assert that we did create the task, but there's no need to store it,
387+
// as it was added to the group itself.
388+
assert(task != nil, "Expected task to be created!")
389+
390+
_startTaskImmediately(task!, targetExecutor: builtinSerialExecutor)
391+
392+
% if IS_ADD_UNLESS_CANCELLED:
393+
return true // task successfully enqueued
394+
% end
228395
}
229396
}
230397
% end # METHOD_NAMES
@@ -236,9 +403,9 @@ extension ${GROUP_TYPE} {
236403
% 'THROWING',
237404
% 'NON_THROWING',
238405
% ]
239-
% for METHOD_VARIANT in METHOD_VARIANTS:
406+
% for THROWING_VARIANT in METHOD_VARIANTS:
240407

241-
% IS_THROWING = METHOD_VARIANT == 'THROWING'
408+
% IS_THROWING = THROWING_VARIANT == 'THROWING'
242409
% if IS_THROWING:
243410
% FAILURE_TYPE = 'Error'
244411
% THROWS = 'throws '
@@ -255,6 +422,7 @@ extension Task where Failure == ${FAILURE_TYPE} {
255422
@MainActor
256423
@available(SwiftStdlib 5.9, *)
257424
@discardableResult
425+
@available(*, deprecated, renamed: "immediate")
258426
public static func startOnMainActor(
259427
priority: TaskPriority? = nil,
260428
@_inheritActorContext @_implicitSelfCapture _ operation: __owned @Sendable @escaping @MainActor () async ${THROWS} -> Success

stdlib/public/Concurrency/Task+init.swift.gyb

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -212,7 +212,7 @@ extension Task where Failure == ${FAILURE_TYPE} {
212212
priority: priority,
213213
isChildTask: false,
214214
copyTaskLocals: ${'true' if not IS_DETACHED else 'false /* detached */'},
215-
inheritContext: true,
215+
inheritContext: ${'true' if not IS_DETACHED else 'false /* detached */'},
216216
enqueueJob: true,
217217
addPendingGroupTaskUnconditionally: false,
218218
isDiscardingTask: false,
@@ -294,8 +294,8 @@ extension Task where Failure == ${FAILURE_TYPE} {
294294
let flags = taskCreateFlags(
295295
priority: priority,
296296
isChildTask: false,
297-
copyTaskLocals: ${'true' if not IS_DETACHED else 'false'},
298-
inheritContext: ${'true' if not IS_DETACHED else 'false'},
297+
copyTaskLocals: ${'true' if not IS_DETACHED else 'false /* detached */'},
298+
inheritContext: ${'true' if not IS_DETACHED else 'false /* detached */'},
299299
enqueueJob: true,
300300
addPendingGroupTaskUnconditionally: false,
301301
isDiscardingTask: false,

stdlib/public/Concurrency/Task.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -864,7 +864,7 @@ swift_task_create_commonImpl(size_t rawTaskCreateFlags,
864864

865865
} else if (taskIsUnstructured(taskCreateFlags, jobFlags)) {
866866
SWIFT_TASK_DEBUG_LOG("Creating an unstructured task from %p%s", currentTask,
867-
taskCreateFlags.isSynchronousStartTask() ? " [start synchronously]" : "");
867+
taskCreateFlags.isImmediateTask() ? " [immediate]" : "");
868868

869869
if (isUnspecified(basePriority)) {
870870
// Case 1: No priority specified

stdlib/public/Concurrency/TaskGroup+addTask.swift.gyb

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -232,7 +232,6 @@ extension ${TYPE} {
232232
/// - name: Human readable name of this task.
233233
% end
234234
% if HAS_TASK_EXECUTOR:
235-
/// - taskExecutor:
236235
/// - taskExecutor: The task executor that the child task should be started on and keep using.
237236
/// Explicitly passing `nil` as the executor preference is equivalent to
238237
/// calling the `${METHOD_NAME}` method without a preference, and effectively
@@ -243,8 +242,6 @@ extension ${TYPE} {
243242
/// - priority: The priority of the operation task.
244243
/// Omit this parameter or pass `nil` to inherit the task group's base priority.
245244
% end
246-
/// Omit this parameter or pass `.unspecified`
247-
/// to set the child task's priority to the priority of the group.
248245
/// - operation: The operation to execute as part of the task group.
249246
% if IS_ADD_UNLESS_CANCELLED:
250247
/// - Returns: `true` if the child task was added to the group;

0 commit comments

Comments
 (0)