Skip to content

Commit a3ccdd2

Browse files
author
Julian Lettner
committed
Teach TSan about Swift Task and Actor model
I have identified the following conceptual synchronization points at which task data and computation can cross thread boundaries. We need to model these in TSan to avoid false positives: Awaiting an async task (`AsyncTask::waitFuture`), which has two cases: 1) The task has completed (`AsyncTask::completeFuture`). Everything that happened during task execution "happened before" before the point where we access its result. We synchronize on the *awaited* task. 2) The task is still executing: the current execution is suspended and the waiting task is put into the list of "waiters". Once the awaited task completes, the waiters will be scheduled. In this case, we synchronize on the *waiting* task. Note: there is a similar relationship for task groups which I still have to investigate. I will follow-up with an additional patch and tests. Actor job execution (`swift::runJobInExecutorContext`): Job scheduling (`swift::swift_task_enqueue`) precedes/happens before job execution. Also all job executions (switching actors or suspend/resume) are serially ordered. Note: the happens-before edge for schedule->execute isn't strictly needed in most cases since scheduling calls through to libdispatch's `dispatch_async_f`, which we already intercept and model in TSan. However, I am trying to model Swift Task semantics to increase the chance of things to continue to work in case the "task backend" is switched out. rdar://74256733
1 parent 5187382 commit a3ccdd2

File tree

2 files changed

+12
-0
lines changed

2 files changed

+12
-0
lines changed

stdlib/public/Concurrency/Actor.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -150,6 +150,8 @@ void swift::swift_job_run(Job *job, ExecutorRef executor) {
150150
}
151151

152152
void swift::runJobInExecutorContext(Job *job, ExecutorRef executor) {
153+
_swift_tsan_acquire(job);
154+
153155
if (auto task = dyn_cast<AsyncTask>(job)) {
154156
// Update the active task in the current thread.
155157
ActiveTask::set(task);
@@ -167,6 +169,8 @@ void swift::runJobInExecutorContext(Job *job, ExecutorRef executor) {
167169
// There's no extra bookkeeping to do for simple jobs.
168170
job->runSimpleInFullyEstablishedContext(executor);
169171
}
172+
173+
_swift_tsan_release(job);
170174
}
171175

172176
AsyncTask *swift::swift_task_getCurrent() {
@@ -1428,6 +1432,8 @@ void swift::swift_task_switch(AsyncTask *task, ExecutorRef currentExecutor,
14281432
void swift::swift_task_enqueue(Job *job, ExecutorRef executor) {
14291433
assert(job && "no job provided");
14301434

1435+
_swift_tsan_release(job);
1436+
14311437
if (executor.isGeneric())
14321438
return swift_task_enqueueGlobal(job);
14331439

stdlib/public/Concurrency/Task.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -63,10 +63,12 @@ FutureFragment::Status AsyncTask::waitFuture(AsyncTask *waitingTask) {
6363
switch (queueHead.getStatus()) {
6464
case Status::Error:
6565
case Status::Success:
66+
_swift_tsan_acquire(static_cast<Job *>(this));
6667
// The task is done; we don't need to wait.
6768
return queueHead.getStatus();
6869

6970
case Status::Executing:
71+
_swift_tsan_release(static_cast<Job *>(waitingTask));
7072
// Task is now complete. We'll need to add ourselves to the queue.
7173
break;
7274
}
@@ -90,6 +92,8 @@ void AsyncTask::completeFuture(AsyncContext *context, ExecutorRef executor) {
9092
using Status = FutureFragment::Status;
9193
using WaitQueueItem = FutureFragment::WaitQueueItem;
9294

95+
_swift_tsan_release(static_cast<Job *>(this));
96+
9397
assert(isFuture());
9498
auto fragment = futureFragment();
9599

@@ -121,6 +125,8 @@ void AsyncTask::completeFuture(AsyncContext *context, ExecutorRef executor) {
121125
// Schedule every waiting task on the executor.
122126
auto waitingTask = queueHead.getTask();
123127
while (waitingTask) {
128+
_swift_tsan_acquire(static_cast<Job *>(waitingTask));
129+
124130
// Find the next waiting task before we invalidate it by resuming
125131
// the task.
126132
auto nextWaitingTask = waitingTask->getNextWaitingTask();

0 commit comments

Comments
 (0)