Skip to content

Commit e75ef10

Browse files
committed
Add pending count overflow protection to TaskGroup
1 parent 028ab32 commit e75ef10

File tree

1 file changed

+56
-0
lines changed

1 file changed

+56
-0
lines changed

stdlib/public/Concurrency/TaskGroup.cpp

Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,12 @@
3939
#include <mutex>
4040
#endif
4141

42+
#if SWIFT_STDLIB_HAS_ASL
43+
#include <asl.h>
44+
#elif defined(__ANDROID__)
45+
#include <android/log.h>
46+
#endif
47+
4248
#include <assert.h>
4349
#if SWIFT_CONCURRENCY_ENABLE_DISPATCH
4450
#include <dispatch/dispatch.h>
@@ -467,6 +473,19 @@ struct TaskGroupStatus {
467473
static const uint64_t maskDiscardingPending = 0b0011111111111111111111111111111111111111111111111111111111111111;
468474
static const uint64_t onePendingTask = 0b0000000000000000000000000000000000000000000000000000000000000001;
469475

476+
/// Depending on kind of task group, we can either support 2^31 or 2^62 pending tasks.
477+
///
478+
/// While a discarding task group's max pending count is unrealistic to be exceeded, the lower
479+
/// maximum number used in an accumulating task group has potential to be exceeded, and thus we must crash
480+
/// rather than start overflowing status if this were to happen.
481+
static uint64_t maximumPendingTasks(TaskGroupBase* group) {
482+
if (group->isAccumulatingResults()) {
483+
return maskAccumulatingPending;
484+
} else {
485+
return maskDiscardingPending;
486+
}
487+
}
488+
470489
uint64_t status;
471490

472491
bool isCancelled() {
@@ -525,6 +544,39 @@ struct TaskGroupStatus {
525544
return TaskGroupStatus{status | (cancel ? cancelled : 0)};
526545
}
527546

547+
static void reportPendingTaskOverflow(TaskGroupBase* group, TaskGroupStatus status) {
548+
char *message;
549+
swift_asprintf(
550+
&message,
551+
"error: %sTaskGroup: detected pending task count overflow, in task group %p! Status: %s",
552+
group->isDiscardingResults() ? "Discarding" : "", group, status.to_string(group).c_str());
553+
554+
if (_swift_shouldReportFatalErrorsToDebugger()) {
555+
RuntimeErrorDetails details = {
556+
.version = RuntimeErrorDetails::currentVersion,
557+
.errorType = "task-group-violation",
558+
.currentStackDescription = "TaskGroup exceeded supported pending task count",
559+
.framesToSkip = 1,
560+
};
561+
_swift_reportToDebugger(RuntimeErrorFlagFatal, message, &details);
562+
}
563+
564+
#if defined(_WIN32)
565+
#define STDERR_FILENO 2
566+
_write(STDERR_FILENO, message, strlen(message));
567+
#else
568+
write(STDERR_FILENO, message, strlen(message));
569+
#endif
570+
#if defined(__APPLE__)
571+
asl_log(nullptr, nullptr, ASL_LEVEL_ERR, "%s", message);
572+
#elif defined(__ANDROID__)
573+
__android_log_print(ANDROID_LOG_FATAL, "SwiftRuntime", "%s", message);
574+
#endif
575+
576+
free(message);
577+
abort();
578+
}
579+
528580
/// Pretty prints the status, as follows:
529581
/// If accumulating results:
530582
/// TaskGroupStatus{ C:{cancelled} W:{waiting task} R:{ready tasks} P:{pending tasks} {binary repr} }
@@ -620,6 +672,10 @@ TaskGroupStatus TaskGroupBase::statusAddPendingTaskAssumeRelaxed(bool unconditio
620672
std::memory_order_relaxed);
621673
auto s = TaskGroupStatus{old + TaskGroupStatus::onePendingTask};
622674

675+
if (s.pendingTasks(this) == TaskGroupStatus::maximumPendingTasks(this)) {
676+
TaskGroupStatus::reportPendingTaskOverflow(this, s); // this will abort()
677+
}
678+
623679
if (!unconditionally && s.isCancelled()) {
624680
// revert that add, it was meaningless
625681
auto o = status.fetch_sub(TaskGroupStatus::onePendingTask,

0 commit comments

Comments
 (0)