Skip to content

Commit 171954a

Browse files
committed
[Concurrency] TaskGroup.addTaskUnlessCancelled with names
1 parent 1ae758c commit 171954a

File tree

2 files changed

+83
-29
lines changed

2 files changed

+83
-29
lines changed

stdlib/public/Concurrency/TaskGroup.swift

Lines changed: 78 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,16 @@ import Swift
1414

1515
// ==== TaskGroup --------------------------------------------------------------
1616

17+
// In the task-to-thread model we don't enqueue tasks created using addTask
18+
// to the global pool, but will run them inline instead.
19+
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
20+
@usableFromInline
21+
let enqueueJobOnCreate = false
22+
#else
23+
@usableFromInline
24+
let enqueueJobOnCreate = true
25+
#endif
26+
1727
/// Starts a new scope that can contain a dynamic number of child tasks.
1828
///
1929
/// A group waits for all of its child tasks
@@ -159,9 +169,9 @@ public func _unsafeInheritExecutor_withTaskGroup<ChildTaskResult, GroupResult>(
159169
/// by calling the `cancelAll()` method on the task group,
160170
/// or by canceling the task in which the group is running.
161171
///
162-
/// If you call `addTask(priority:operation:)` to create a new task in a canceled group,
172+
/// If you call `addTask(name:priority:operation:)` to create a new task in a canceled group,
163173
/// that task is immediately canceled after creation.
164-
/// Alternatively, you can call `addTaskUnlessCancelled(priority:operation:)`,
174+
/// Alternatively, you can call `addTaskUnlessCancelled(name:priority:operation:)`,
165175
/// which doesn't create the task if the group has already been canceled.
166176
/// Choosing between these two functions
167177
/// lets you control how to react to cancellation within a group:
@@ -307,8 +317,8 @@ public func _unsafeInheritExecutor_withThrowingTaskGroup<ChildTaskResult, GroupR
307317
///
308318
/// A cancelled task group can still keep adding tasks, however they will start
309319
/// being immediately cancelled, and may act accordingly to this. To avoid adding
310-
/// new tasks to an already cancelled task group, use ``addTaskUnlessCancelled(priority:body:)``
311-
/// rather than the plain ``addTask(priority:body:)`` which adds tasks unconditionally.
320+
/// new tasks to an already cancelled task group, use ``addTaskUnlessCancelled(name:priority:body:)``
321+
/// rather than the plain ``addTask(name:priority:body:)`` which adds tasks unconditionally.
312322
///
313323
/// For information about the language-level concurrency model that `TaskGroup` is part of,
314324
/// see [Concurrency][concurrency] in [The Swift Programming Language][tspl].
@@ -344,20 +354,12 @@ public struct TaskGroup<ChildTaskResult: Sendable> {
344354
priority: TaskPriority? = nil,
345355
operation: sending @escaping @isolated(any) () async -> ChildTaskResult
346356
) {
347-
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
348357
let flags = taskCreateFlags(
349358
priority: priority, isChildTask: true, copyTaskLocals: false,
350-
inheritContext: false, enqueueJob: false,
359+
inheritContext: false, enqueueJob: enqueueJobOnCreate,
351360
addPendingGroupTaskUnconditionally: true,
352361
isDiscardingTask: false
353362
)
354-
#else
355-
let flags = taskCreateFlags(
356-
priority: priority, isChildTask: true, copyTaskLocals: false,
357-
inheritContext: false, enqueueJob: true,
358-
addPendingGroupTaskUnconditionally: true,
359-
isDiscardingTask: false)
360-
#endif
361363

362364
// Create the task in this group.
363365
let builtinSerialExecutor =
@@ -383,15 +385,9 @@ public struct TaskGroup<ChildTaskResult: Sendable> {
383385
priority: TaskPriority? = nil,
384386
operation: sending @escaping @isolated(any) () async -> ChildTaskResult
385387
) {
386-
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
387-
let enqueueJob = false
388-
#else
389-
let enqueueJob = true
390-
#endif
391-
392388
let flags = taskCreateFlags(
393389
priority: priority, isChildTask: true, copyTaskLocals: false,
394-
inheritContext: false, enqueueJob: enqueueJob,
390+
inheritContext: false, enqueueJob: enqueueJobOnCreate,
395391
addPendingGroupTaskUnconditionally: true,
396392
isDiscardingTask: false
397393
)
@@ -448,19 +444,11 @@ public struct TaskGroup<ChildTaskResult: Sendable> {
448444
// the group is cancelled and is not accepting any new work
449445
return false
450446
}
451-
#if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
452447
let flags = taskCreateFlags(
453448
priority: priority, isChildTask: true, copyTaskLocals: false,
454-
inheritContext: false, enqueueJob: false,
449+
inheritContext: false, enqueueJob: enqueueJobOnCreate,
455450
addPendingGroupTaskUnconditionally: false,
456451
isDiscardingTask: false)
457-
#else
458-
let flags = taskCreateFlags(
459-
priority: priority, isChildTask: true, copyTaskLocals: false,
460-
inheritContext: false, enqueueJob: true,
461-
addPendingGroupTaskUnconditionally: false,
462-
isDiscardingTask: false)
463-
#endif
464452

465453
// Create the task in this group.
466454
let builtinSerialExecutor =
@@ -473,6 +461,67 @@ public struct TaskGroup<ChildTaskResult: Sendable> {
473461
return true
474462
}
475463

464+
/// Adds a child task to the group, unless the group has been canceled.
465+
///
466+
/// - Parameters:
467+
/// - name: Human readable name of the task.
468+
/// - priority: The priority of the operation task.
469+
/// Omit this parameter or pass `.unspecified`
470+
/// to set the child task's priority to the priority of the group.
471+
/// - operation: The operation to execute as part of the task group.
472+
/// - Returns: `true` if the child task was added to the group;
473+
/// otherwise `false`.
474+
@_alwaysEmitIntoClient
475+
public mutating func addTaskUnlessCancelled(
476+
name: String? = nil,
477+
priority: TaskPriority? = nil,
478+
operation: sending @escaping @isolated(any) () async -> ChildTaskResult
479+
) -> Bool {
480+
let canAdd = _taskGroupAddPendingTask(group: _group, unconditionally: false)
481+
482+
guard canAdd else {
483+
// the group is cancelled and is not accepting any new work
484+
return false
485+
}
486+
let flags = taskCreateFlags(
487+
priority: priority, isChildTask: true, copyTaskLocals: false,
488+
inheritContext: false, enqueueJob: enqueueJobOnCreate,
489+
addPendingGroupTaskUnconditionally: false,
490+
isDiscardingTask: false)
491+
492+
let builtinSerialExecutor =
493+
Builtin.extractFunctionIsolation(operation)?.unownedExecutor.executor
494+
495+
var task: Builtin.NativeObject?
496+
#if $BuiltinCreateAsyncTaskName
497+
if var name {
498+
task =
499+
name.withUTF8 { nameBytes in
500+
Builtin.createTask(
501+
flags: flags,
502+
initialSerialExecutor: builtinSerialExecutor,
503+
taskGroup: _group,
504+
taskName: nameBytes.baseAddress?._rawValue,
505+
operation: operation).0
506+
}
507+
}
508+
#endif
509+
510+
if task == nil {
511+
// either no task name was set, or names are unsupported
512+
task = Builtin.createTask(
513+
flags: flags,
514+
// unsupported names, so we drop it.
515+
initialSerialExecutor: builtinSerialExecutor,
516+
operation: operation).0
517+
}
518+
519+
// task was enqueued to the group, no need to store the 'task' ref itself
520+
assert(task != nil, "Expected task to be created!")
521+
522+
return true
523+
}
524+
476525
#else // if SWIFT_STDLIB_TASK_TO_THREAD_MODEL_CONCURRENCY
477526
@available(SwiftStdlib 5.7, *)
478527
@available(*, unavailable, message: "Unavailable in task-to-thread concurrency model", renamed: "addTask(operation:)")

test/Concurrency/Runtime/async_task_naming.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,11 @@ func test() async {
2828
print("Task.name = \(Task.name ?? "NONE")")
2929
return 12
3030
}
31+
g.addTaskUnlessCancelled(name: "Caplin the TaskGroup Task (unless cancelled)") {
32+
// CHECK: Task.name = Caplin the TaskGroup Task (unless cancelled)
33+
print("Task.name = \(Task.name ?? "NONE")")
34+
return 12
35+
}
3136
}
3237

3338
// _ = await withThrowingTaskGroup(of: Int.self) { g in

0 commit comments

Comments
 (0)