Skip to content

Commit d8c51d0

Browse files
committed
[TaskGroup] Must detach discarded task, THEN unlock before resume waiting
1 parent 9fecd4b commit d8c51d0

File tree

1 file changed

+41
-21
lines changed

1 file changed

+41
-21
lines changed

stdlib/public/Concurrency/TaskGroup.cpp

Lines changed: 41 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1158,7 +1158,7 @@ void AccumulatingTaskGroup::offer(AsyncTask *completedTask, AsyncContext *contex
11581158
// This is wasteful, and the task completion function should be fixed to
11591159
// transfer ownership of a retain into this function, in which case we
11601160
// will need to release in the other path.
1161-
lock(); // TODO: remove fragment lock, and use status for synchronization
1161+
lock();
11621162

11631163
SWIFT_TASK_GROUP_DEBUG_LOG(this, "offer, completedTask:%p, status:%s",
11641164
completedTask,
@@ -1190,9 +1190,9 @@ void AccumulatingTaskGroup::offer(AsyncTask *completedTask, AsyncContext *contex
11901190

11911191
// ==== a) has waiting task, so let us complete it right away
11921192
if (assumed.hasWaitingTask()) {
1193-
resumeWaitingTask(completedTask, assumed, hadErrorResult);
1194-
unlock(); // TODO: remove fragment lock, and use status for synchronization
1195-
return;
1193+
// Must unlock before we resume the waiting task
1194+
unlock();
1195+
return resumeWaitingTask(completedTask, assumed, hadErrorResult);
11961196
} else {
11971197
// ==== b) enqueue completion ------------------------------------------------
11981198
//
@@ -1202,7 +1202,7 @@ void AccumulatingTaskGroup::offer(AsyncTask *completedTask, AsyncContext *contex
12021202
assert(!waitQueue.load(std::memory_order_relaxed));
12031203

12041204
enqueueCompletedTask(completedTask, hadErrorResult);
1205-
unlock(); // TODO: remove fragment lock, and use status for synchronization
1205+
return unlock();
12061206
}
12071207
}
12081208

@@ -1253,41 +1253,54 @@ void DiscardingTaskGroup::offer(AsyncTask *completedTask, AsyncContext *context)
12531253
switch (readyErrorItem.getStatus()) {
12541254
case ReadyStatus::RawError:
12551255
SWIFT_TASK_GROUP_DEBUG_LOG(this, "offer, complete, resume with raw error:%p", readyErrorItem.getRawError(this));
1256-
resumeWaitingTaskWithError(readyErrorItem.getRawError(this), assumed,
1256+
// The following MUST be done in the following order: detach, unlock, resume waitingTask.
1257+
// because we do not want to allow another task to run and have the potential to lock or even destroy
1258+
// the group before we've given up the lock.
1259+
_swift_taskGroup_detachChild(asAbstract(this), completedTask);
1260+
unlock();
1261+
return resumeWaitingTaskWithError(readyErrorItem.getRawError(this), assumed,
12571262
alreadyDecrementedStatus);
1258-
break;
12591263
case ReadyStatus::Error:
12601264
// The completed task failed, but we already stored a different failed task.
12611265
// Thus we discard this error and complete with the previously stored.
12621266
SWIFT_TASK_GROUP_DEBUG_LOG(this, "offer, complete, discard error completedTask %p, resume with errorItem.task:%p",
12631267
completedTask,
12641268
readyErrorItem.getTask());
1269+
// The following MUST be done in the following order: detach, unlock, resume waitingTask.
1270+
// because we do not want to allow another task to run and have the potential to lock or even destroy
1271+
// the group before we've given up the lock.
12651272
_swift_taskGroup_detachChild(asAbstract(this), completedTask);
1266-
resumeWaitingTask(readyErrorItem.getTask(), assumed,
1273+
unlock();
1274+
return resumeWaitingTask(readyErrorItem.getTask(), assumed,
12671275
/*hadErrorResult=*/true,
12681276
alreadyDecrementedStatus,
12691277
/*taskWasRetained=*/true);
1270-
break;
12711278
default:
12721279
swift_Concurrency_fatalError(0,
12731280
"only errors can be stored by a discarding task group, yet it wasn't an error! 1");
12741281
}
12751282
} else {
1283+
// The following MUST be done in the following order: detach, unlock, resume waitingTask.
1284+
// because we do not want to allow another task to run and have the potential to lock or even destroy
1285+
// the group before we've given up the lock.
1286+
_swift_taskGroup_detachChild(asAbstract(this), completedTask);
1287+
unlock();
12761288
// There was no prior failed task stored, so we should resume the waitingTask with this (failed) completedTask
1277-
resumeWaitingTask(completedTask, assumed, hadErrorResult, alreadyDecrementedStatus);
1289+
return resumeWaitingTask(completedTask, assumed, hadErrorResult, alreadyDecrementedStatus);
12781290
}
12791291
} else if (readyQueue.isEmpty()) {
12801292
// There was no waiting task, or other tasks are still pending, so we cannot
12811293
// it is the first error we encountered, thus we need to store it for future throwing
12821294
SWIFT_TASK_GROUP_DEBUG_LOG(this, "offer, enqueue child task:%p", completedTask);
12831295
enqueueCompletedTask(completedTask, hadErrorResult);
1296+
return unlock();
12841297
} else {
12851298
SWIFT_TASK_GROUP_DEBUG_LOG(this, "offer, complete, discard child task:%p", completedTask);
12861299
_swift_taskGroup_detachChild(asAbstract(this), completedTask);
1300+
return unlock();
12871301
}
1288-
1289-
unlock();
1290-
return;
1302+
swift_Concurrency_fatalError(0, "expected to early return from when "
1303+
"handling offer of last task in group");
12911304
}
12921305

12931306
assert(!hadErrorResult && "only successfully completed tasks can reach here");
@@ -1302,31 +1315,38 @@ void DiscardingTaskGroup::offer(AsyncTask *completedTask, AsyncContext *context)
13021315
_swift_taskGroup_detachChild(asAbstract(this), completedTask);
13031316
switch (readyErrorItem.getStatus()) {
13041317
case ReadyStatus::RawError:
1305-
resumeWaitingTaskWithError(readyErrorItem.getRawError(this), assumed, alreadyDecrementedStatus);
1306-
break;
1318+
// The following MUST be done in the following order: detach, unlock, resume waitingTask.
1319+
// because we do not want to allow another task to run and have the potential to lock or even destroy
1320+
// the group before we've given up the lock.
1321+
_swift_taskGroup_detachChild(asAbstract(this), completedTask);
1322+
unlock();
1323+
return resumeWaitingTaskWithError(readyErrorItem.getRawError(this), assumed, alreadyDecrementedStatus);
13071324
case ReadyStatus::Error:
1308-
resumeWaitingTask(readyErrorItem.getTask(), assumed,
1325+
// The following MUST be done in the following order: detach, unlock, resume waitingTask.
1326+
// because we do not want to allow another task to run and have the potential to lock or even destroy
1327+
// the group before we've given up the lock.
1328+
_swift_taskGroup_detachChild(asAbstract(this), completedTask);
1329+
unlock();
1330+
return resumeWaitingTask(readyErrorItem.getTask(), assumed,
13091331
/*hadErrorResult=*/true,
13101332
alreadyDecrementedStatus,
13111333
/*taskWasRetained=*/true);
1312-
break;
13131334
default:
13141335
swift_Concurrency_fatalError(0,
13151336
"only errors can be stored by a discarding task group, yet it wasn't an error! 2");
13161337
}
13171338
} else {
1339+
unlock();
13181340
// This is the last task, we have a waiting task and there was no error stored previously;
13191341
// We must resume the waiting task with a success, so let us return here.
1320-
resumeWaitingTask(completedTask, assumed, /*hadErrorResult=*/false, alreadyDecrementedStatus);
1342+
return resumeWaitingTask(completedTask, assumed, /*hadErrorResult=*/false, alreadyDecrementedStatus);
13211343
}
13221344
} else {
13231345
// it wasn't the last pending task, and there is no-one to resume;
13241346
// Since this is a successful result, and we're a discarding task group -- always just ignore this task.
13251347
_swift_taskGroup_detachChild(asAbstract(this), completedTask);
1348+
return unlock();
13261349
}
1327-
1328-
unlock();
1329-
return;
13301350
}
13311351

13321352
/// Must be called while holding the TaskGroup lock.

0 commit comments

Comments
 (0)