Skip to content

Commit aca284e

Browse files
committed
[Concurrency] DiscardingTG error thrown from body always wins
1 parent f9915cf commit aca284e

File tree

2 files changed

+29
-13
lines changed

2 files changed

+29
-13
lines changed

stdlib/public/Concurrency/TaskGroup.cpp

Lines changed: 28 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,21 @@ fprintf(stderr, "[%#lx] [%s:%d][group(%p%s)] (%s) " fmt "\n", \
6464

6565
using FutureFragment = AsyncTask::FutureFragment;
6666

67+
/// During evolution discussions we opted to implement the following semantic of
68+
/// a discarding task-group throw:
69+
/// - the error thrown out of withThrowingDiscardingTaskGroup { ... } always "wins",
70+
/// even if the group already had an error stored within.
71+
///
72+
/// This is harder to implement, since we have to always store the "first error from children",
73+
/// and keep it around until body completes, and only then are we able to decide which error to
74+
/// re-throw; If we threw the body task, we must swift_release the stored "first child error" (if it was present).
75+
///
76+
/// Implementation of "rethrow the first child error" just works in `waitAll`,
77+
/// since we poll the error and resume the waiting task with it immediately.
78+
///
79+
/// Change this flag, or expose a boolean to offer developers a choice of behavior.
80+
#define SWIFT_TASK_GROUP_BODY_THROWN_ERROR_WINS 1
81+
6782
namespace {
6883
class TaskStatusRecord;
6984
struct TaskGroupStatus;
@@ -237,10 +252,6 @@ class TaskGroupBase : public TaskGroupTaskStatusRecord {
237252
}
238253

239254
static ReadyQueueItem get(ReadyStatus status, AsyncTask *task) {
240-
fprintf(stderr, "[%s:%d](%s) store ReadyQueueItem = %s\n", __FILE_NAME__, __LINE__, __FUNCTION__,
241-
status == ReadyStatus::Error ? "error" :
242-
(status == ReadyStatus::RawError ? "raw-error" :
243-
(status == ReadyStatus::Success ? "success" : "other")));
244255
assert(task == nullptr || task->isFuture());
245256
return ReadyQueueItem{
246257
reinterpret_cast<uintptr_t>(task) | static_cast<uintptr_t>(status)};
@@ -983,7 +994,8 @@ static void fillGroupNextResult(TaskFutureWaitAsyncContext *context,
983994
return;
984995

985996
case PollStatus::Error: {
986-
fillGroupNextErrorResult(context, reinterpret_cast<SwiftError *>(result.storage));
997+
auto error = reinterpret_cast<SwiftError *>(result.storage);
998+
fillGroupNextErrorResult(context, error);
987999
return;
9881000
}
9891001

@@ -1659,9 +1671,17 @@ static void swift_taskGroup_waitAllImpl(
16591671
#endif /* __ARM_ARCH_7K__ */
16601672

16611673
case PollStatus::Error:
1662-
SWIFT_TASK_GROUP_DEBUG_LOG(group, "waitAll found error, waiting task = %p, status:%s",
1663-
waitingTask, group->statusString().c_str());
1674+
SWIFT_TASK_GROUP_DEBUG_LOG(group, "waitAll found error, waiting task = %p, body error = %p, status:%s",
1675+
waitingTask, bodyError, group->statusString().c_str());
1676+
#if SWIFT_TASK_GROUP_BODY_THROWN_ERROR_WINS
1677+
if (bodyError) {
1678+
fillGroupNextErrorResult(context, bodyError);
1679+
} else {
1680+
fillGroupNextResult(context, polled);
1681+
}
1682+
#else // so, not SWIFT_TASK_GROUP_BODY_THROWN_ERROR_WINS
16641683
fillGroupNextResult(context, polled);
1684+
#endif // SWIFT_TASK_GROUP_BODY_THROWN_ERROR_WINS
16651685
if (auto completedTask = polled.retainedTask) {
16661686
// Remove the child from the task group's running tasks list.
16671687
_swift_taskGroup_detachChild(asAbstract(group), completedTask);
@@ -1739,9 +1759,6 @@ PollResult TaskGroupBase::waitAll(AsyncTask *waitingTask) {
17391759
auto discardingGroup = asDiscardingImpl(this);
17401760
ReadyQueueItem firstErrorItem;
17411761
if (readyQueue.dequeue(firstErrorItem)) {
1742-
fprintf(stderr, "[%s:%d](%s) waitAll EMPTY AND dequeued from readyQueue: %s\n", __FILE_NAME__, __LINE__, __FUNCTION__,
1743-
(firstErrorItem.getStatus() == ReadyStatus::Error ? "error" :
1744-
(firstErrorItem.getStatus() == ReadyStatus::RawError) ? "raw-error" : "wat"));
17451762
if (firstErrorItem.getStatus() == ReadyStatus::Error) {
17461763
result = PollResult::get(firstErrorItem.getTask(), /*hadErrorResult=*/true);
17471764
} else if (firstErrorItem.getStatus() == ReadyStatus::RawError) {
@@ -1754,7 +1771,7 @@ PollResult TaskGroupBase::waitAll(AsyncTask *waitingTask) {
17541771
return result;
17551772
}
17561773

1757-
SWIFT_TASK_GROUP_DEBUG_LOG(this, "group is empty, no pending tasks, status = %s", assumed.to_string(this));
1774+
SWIFT_TASK_GROUP_DEBUG_LOG(this, "group is empty, no pending tasks, status = %s", assumed.to_string(this).c_str());
17581775
// No tasks in flight, we know no tasks were submitted before this poll
17591776
// was issued, and if we parked here we'd potentially never be woken up.
17601777
// Bail out and return `nil` from `group.next()`.

test/Concurrency/Runtime/async_taskgroup_throw_rethrow.swift

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,6 @@
44
// REQUIRES: concurrency
55
// REQUIRES: reflection
66

7-
// REQUIRES: rdar104212282
87
// rdar://76038845
98
// REQUIRES: concurrency_runtime
109
// UNSUPPORTED: back_deployment_runtime
@@ -173,7 +172,7 @@ func test_discardingTaskGroup_automaticallyRethrows_first_withThrowingBodySecond
173172
// CHECK: Throwing: Boom(id: "task, first, isCancelled:false
174173
// CHECK: Throwing: Boom(id: "body, second, isCancelled:true
175174
// and only then the re-throw happens:
176-
// CHECK: rethrown: Boom(id: "task, first, isCancelled:false
175+
// CHECK: rethrown: Boom(id: "body, second
177176
print("rethrown: \(error)")
178177
}
179178
}

0 commit comments

Comments
 (0)