Skip to content

Commit ee6b902

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

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)};
@@ -976,7 +987,8 @@ static void fillGroupNextResult(TaskFutureWaitAsyncContext *context,
976987
return;
977988

978989
case PollStatus::Error: {
979-
fillGroupNextErrorResult(context, reinterpret_cast<SwiftError *>(result.storage));
990+
auto error = reinterpret_cast<SwiftError *>(result.storage);
991+
fillGroupNextErrorResult(context, error);
980992
return;
981993
}
982994

@@ -1652,9 +1664,17 @@ static void swift_taskGroup_waitAllImpl(
16521664
#endif /* __ARM_ARCH_7K__ */
16531665

16541666
case PollStatus::Error:
1655-
SWIFT_TASK_GROUP_DEBUG_LOG(group, "waitAll found error, waiting task = %p, status:%s",
1656-
waitingTask, group->statusString().c_str());
1667+
SWIFT_TASK_GROUP_DEBUG_LOG(group, "waitAll found error, waiting task = %p, body error = %p, status:%s",
1668+
waitingTask, bodyError, group->statusString().c_str());
1669+
#if SWIFT_TASK_GROUP_BODY_THROWN_ERROR_WINS
1670+
if (bodyError) {
1671+
fillGroupNextErrorResult(context, bodyError);
1672+
} else {
1673+
fillGroupNextResult(context, polled);
1674+
}
1675+
#else // so, not SWIFT_TASK_GROUP_BODY_THROWN_ERROR_WINS
16571676
fillGroupNextResult(context, polled);
1677+
#endif // SWIFT_TASK_GROUP_BODY_THROWN_ERROR_WINS
16581678
if (auto completedTask = polled.retainedTask) {
16591679
// Remove the child from the task group's running tasks list.
16601680
_swift_taskGroup_detachChild(asAbstract(group), completedTask);
@@ -1732,9 +1752,6 @@ PollResult TaskGroupBase::waitAll(AsyncTask *waitingTask) {
17321752
auto discardingGroup = asDiscardingImpl(this);
17331753
ReadyQueueItem firstErrorItem;
17341754
if (readyQueue.dequeue(firstErrorItem)) {
1735-
fprintf(stderr, "[%s:%d](%s) waitAll EMPTY AND dequeued from readyQueue: %s\n", __FILE_NAME__, __LINE__, __FUNCTION__,
1736-
(firstErrorItem.getStatus() == ReadyStatus::Error ? "error" :
1737-
(firstErrorItem.getStatus() == ReadyStatus::RawError) ? "raw-error" : "wat"));
17381755
if (firstErrorItem.getStatus() == ReadyStatus::Error) {
17391756
result = PollResult::get(firstErrorItem.getTask(), /*hadErrorResult=*/true);
17401757
} else if (firstErrorItem.getStatus() == ReadyStatus::RawError) {
@@ -1747,7 +1764,7 @@ PollResult TaskGroupBase::waitAll(AsyncTask *waitingTask) {
17471764
return result;
17481765
}
17491766

1750-
SWIFT_TASK_GROUP_DEBUG_LOG(this, "group is empty, no pending tasks, status = %s", assumed.to_string(this));
1767+
SWIFT_TASK_GROUP_DEBUG_LOG(this, "group is empty, no pending tasks, status = %s", assumed.to_string(this).c_str());
17511768
// No tasks in flight, we know no tasks were submitted before this poll
17521769
// was issued, and if we parked here we'd potentially never be woken up.
17531770
// 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)