Skip to content

Commit 5499235

Browse files
authored
Merge pull request swiftlang#36216 from apple/tsan-swift-concurrency2
2 parents 2a46f5f + 8abf12f commit 5499235

File tree

12 files changed

+528
-1
lines changed

12 files changed

+528
-1
lines changed

stdlib/public/Concurrency/Actor.cpp

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

147147
void swift::runJobInExecutorContext(Job *job, ExecutorRef executor) {
148+
_swift_tsan_acquire(job);
149+
148150
if (auto task = dyn_cast<AsyncTask>(job)) {
149151
// Update the active task in the current thread.
150152
ActiveTask::set(task);
@@ -162,6 +164,8 @@ void swift::runJobInExecutorContext(Job *job, ExecutorRef executor) {
162164
// There's no extra bookkeeping to do for simple jobs.
163165
job->runSimpleInFullyEstablishedContext(executor);
164166
}
167+
168+
_swift_tsan_release(job);
165169
}
166170

167171
AsyncTask *swift::swift_task_getCurrent() {
@@ -1435,6 +1439,8 @@ void swift::swift_task_switch(AsyncTask *task, ExecutorRef currentExecutor,
14351439
void swift::swift_task_enqueue(Job *job, ExecutorRef executor) {
14361440
assert(job && "no job provided");
14371441

1442+
_swift_tsan_release(job);
1443+
14381444
if (executor.isGeneric())
14391445
return swift_task_enqueueGlobal(job);
14401446

stdlib/public/Concurrency/CMakeLists.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -65,6 +65,7 @@ add_swift_target_library(swift_Concurrency ${SWIFT_STDLIB_LIBRARY_BUILD_TYPES} I
6565
TaskGroup.swift
6666
TaskLocal.cpp
6767
TaskLocal.swift
68+
ThreadSanitizer.cpp
6869
Mutex.cpp
6970
${swift_concurrency_objc_sources}
7071

stdlib/public/Concurrency/Task.cpp

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

7071
case Status::Executing:
72+
_swift_tsan_release(static_cast<Job *>(waitingTask));
7173
// Task is now complete. We'll need to add ourselves to the queue.
7274
break;
7375
}
@@ -102,6 +104,8 @@ void AsyncTask::completeFuture(AsyncContext *context, ExecutorRef executor) {
102104
hadErrorResult = true;
103105
}
104106

107+
_swift_tsan_release(static_cast<Job *>(this));
108+
105109
// Update the status to signal completion.
106110
auto newQueueHead = WaitQueueItem::get(
107111
hadErrorResult ? Status::Error : Status::Success,
@@ -135,6 +139,8 @@ void AsyncTask::completeFuture(AsyncContext *context, ExecutorRef executor) {
135139
waitingContext->fillWithSuccess(fragment);
136140
}
137141

142+
_swift_tsan_acquire(static_cast<Job *>(waitingTask));
143+
138144
// Enqueue the waiter on the global executor.
139145
// TODO: allow waiters to fill in a suggested executor
140146
swift_task_enqueueGlobal(waitingTask);

stdlib/public/Concurrency/TaskPrivate.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -56,6 +56,11 @@ void donateThreadToGlobalExecutorUntil(bool (*condition)(void*),
5656
void *context);
5757
#endif
5858

59+
/// release() establishes a happens-before relation with a preceding acquire()
60+
/// on the same address.
61+
void _swift_tsan_acquire(void *addr);
62+
void _swift_tsan_release(void *addr);
63+
5964
// ==== ------------------------------------------------------------------------
6065

6166
namespace {
Lines changed: 63 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,63 @@
1+
//===--- ThreadSanitizer.cpp - Thread Sanitizer support -------------------===//
2+
//
3+
// This source file is part of the Swift.org open source project
4+
//
5+
// Copyright (c) 2014 - 2021 Apple Inc. and the Swift project authors
6+
// Licensed under Apache License v2.0 with Runtime Library Exception
7+
//
8+
// See https://swift.org/LICENSE.txt for license information
9+
// See https://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
10+
//
11+
//===----------------------------------------------------------------------===//
12+
//
13+
// Thread Sanitizer support for the Swift Task runtime
14+
//
15+
//===----------------------------------------------------------------------===//
16+
17+
#include "TaskPrivate.h"
18+
19+
#if defined(_WIN32)
20+
#define NOMINMAX
21+
#include <windows.h>
22+
#else
23+
#include <dlfcn.h>
24+
#endif
25+
26+
namespace {
27+
using TSanFunc = void(void *);
28+
TSanFunc *tsan_acquire, *tsan_release;
29+
30+
TSanFunc *loadSymbol(const char *name) {
31+
#if defined(_WIN32)
32+
return (TSanFunc *)GetProcAddress(GetModuleHandle(NULL), name);
33+
#else
34+
return (TSanFunc *)dlsym(RTLD_DEFAULT, name);
35+
#endif
36+
}
37+
38+
swift::swift_once_t initOnceToken;
39+
void initializeThreadSanitizer(void *unused) {
40+
tsan_acquire = loadSymbol("__tsan_acquire");
41+
tsan_release = loadSymbol("__tsan_release");
42+
}
43+
} // anonymous namespace
44+
45+
void swift::_swift_tsan_acquire(void *addr) {
46+
swift_once(&initOnceToken, initializeThreadSanitizer, nullptr);
47+
if (tsan_acquire) {
48+
tsan_acquire(addr);
49+
}
50+
}
51+
52+
void swift::_swift_tsan_release(void *addr) {
53+
swift_once(&initOnceToken, initializeThreadSanitizer, nullptr);
54+
if (tsan_release) {
55+
tsan_release(addr);
56+
}
57+
}
58+
59+
SWIFT_EXPORT_FROM(swift_Concurrency) SWIFT_CC(c)
60+
void swift_task_set_tsan_hooks(TSanFunc *acquire, TSanFunc *release) {
61+
tsan_acquire = acquire;
62+
tsan_release = release;
63+
}

test/Concurrency/Runtime/actor_counters.swift

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -63,6 +63,12 @@ func runTest(numCounters: Int, numWorkers: Int, numIterations: Int) async {
6363

6464
@main struct Main {
6565
static func main() async {
66-
await runTest(numCounters: 10, numWorkers: 100, numIterations: 1000)
66+
// Useful for debugging: specify counter/worker/iteration counts
67+
let args = CommandLine.arguments
68+
let counters = args.count >= 2 ? Int(args[1])! : 10
69+
let workers = args.count >= 3 ? Int(args[2])! : 100
70+
let iterations = args.count >= 4 ? Int(args[3])! : 1000
71+
print("counters: \(counters), workers: \(workers), iterations: \(iterations)")
72+
await runTest(numCounters: counters, numWorkers: workers, numIterations: iterations)
6773
}
6874
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency %import-libdispatch -parse-as-library -sanitize=thread)
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: libdispatch
6+
// REQUIRES: tsan_runtime
7+
// UNSUPPORTED: linux
8+
// UNSUPPORTED: windows
9+
10+
#if canImport(Darwin)
11+
import Darwin
12+
#elseif canImport(Glibc)
13+
import Glibc
14+
#endif
15+
16+
actor Counter {
17+
private var value = 0
18+
private let scratchBuffer: UnsafeMutableBufferPointer<Int>
19+
20+
init(maxCount: Int) {
21+
scratchBuffer = .allocate(capacity: maxCount)
22+
scratchBuffer.initialize(repeating: 0)
23+
}
24+
25+
func next() -> Int {
26+
let current = value
27+
28+
// Make sure we haven't produced this value before
29+
assert(scratchBuffer[current] == 0)
30+
scratchBuffer[current] = 1
31+
32+
value = value + 1
33+
return current
34+
}
35+
}
36+
37+
38+
func worker(identity: Int, counters: [Counter], numIterations: Int) async {
39+
for i in 0..<numIterations {
40+
let counterIndex = Int.random(in: 0 ..< counters.count)
41+
let counter = counters[counterIndex]
42+
let nextValue = await counter.next()
43+
print("Worker \(identity) calling counter \(counterIndex) produced \(nextValue)")
44+
}
45+
}
46+
47+
func runTest(numCounters: Int, numWorkers: Int, numIterations: Int) async {
48+
// Create counter actors.
49+
var counters: [Counter] = []
50+
for i in 0..<numCounters {
51+
counters.append(Counter(maxCount: numWorkers * numIterations))
52+
}
53+
54+
// Create a bunch of worker threads.
55+
var workers: [Task.Handle<Void, Error>] = []
56+
for i in 0..<numWorkers {
57+
workers.append(
58+
Task.runDetached { [counters] in
59+
usleep(UInt32.random(in: 0..<100) * 1000)
60+
await worker(identity: i, counters: counters, numIterations: numIterations)
61+
}
62+
)
63+
}
64+
65+
// Wait until all of the workers have finished.
66+
for worker in workers {
67+
try! await worker.get()
68+
}
69+
70+
print("DONE!")
71+
}
72+
73+
@main struct Main {
74+
static func main() async {
75+
// Useful for debugging: specify counter/worker/iteration counts
76+
let args = CommandLine.arguments
77+
let counters = args.count >= 2 ? Int(args[1])! : 10
78+
let workers = args.count >= 3 ? Int(args[2])! : 100
79+
let iterations = args.count >= 4 ? Int(args[3])! : 1000
80+
print("counters: \(counters), workers: \(workers), iterations: \(iterations)")
81+
await runTest(numCounters: counters, numWorkers: workers, numIterations: iterations)
82+
}
83+
}
Lines changed: 56 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,56 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency %import-libdispatch -parse-as-library -sanitize=thread)
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: libdispatch
6+
// REQUIRES: tsan_runtime
7+
8+
#if canImport(Darwin)
9+
import Darwin
10+
#elseif canImport(Glibc)
11+
import Glibc
12+
#endif
13+
14+
func fib(_ n: Int) -> Int {
15+
var first = 0
16+
var second = 1
17+
for _ in 0..<n {
18+
let temp = first
19+
first = second
20+
second = temp + first
21+
}
22+
return first
23+
}
24+
25+
func asyncFib(_ n: Int) async -> Int {
26+
if n == 0 || n == 1 {
27+
return n
28+
}
29+
30+
async let first = await asyncFib(n-2)
31+
async let second = await asyncFib(n-1)
32+
33+
// Sleep a random amount of time waiting on the result producing a result.
34+
usleep(UInt32.random(in: 0..<100) * 1000)
35+
36+
let result = await first + second
37+
38+
// Sleep a random amount of time before producing a result.
39+
usleep(UInt32.random(in: 0..<100) * 1000)
40+
41+
return result
42+
}
43+
44+
func runFibonacci(_ n: Int) async {
45+
let result = await asyncFib(n)
46+
47+
print()
48+
print("Async fib = \(result), sequential fib = \(fib(n))")
49+
assert(result == fib(n))
50+
}
51+
52+
@main struct Main {
53+
static func main() async {
54+
await runFibonacci(10)
55+
}
56+
}
Lines changed: 85 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,85 @@
1+
// RUN: %target-run-simple-swift(-Xfrontend -enable-experimental-concurrency %import-libdispatch -parse-as-library -sanitize=thread)
2+
3+
// REQUIRES: executable_test
4+
// REQUIRES: concurrency
5+
// REQUIRES: libdispatch
6+
// REQUIRES: tsan_runtime
7+
8+
import Dispatch
9+
10+
#if canImport(Darwin)
11+
import Darwin
12+
#elseif canImport(Glibc)
13+
import Glibc
14+
#endif
15+
16+
enum HomeworkError: Error, Equatable {
17+
case dogAteIt(String)
18+
}
19+
20+
func formGreeting(name: String) async -> String {
21+
return "Hello \(name) from async world"
22+
}
23+
24+
func testSimple(
25+
name: String, dogName: String, shouldThrow: Bool, doSuspend: Bool
26+
) async {
27+
print("Testing name: \(name), dog: \(dogName), shouldThrow: \(shouldThrow) doSuspend: \(doSuspend)")
28+
29+
var completed = false
30+
31+
let taskHandle: Task.Handle<String, Error> = Task.runDetached {
32+
let greeting = await formGreeting(name: name)
33+
34+
// If the intent is to test suspending, wait a bit so the second task
35+
// can complete.
36+
if doSuspend {
37+
print("- Future sleeping")
38+
sleep(1)
39+
}
40+
41+
if (shouldThrow) {
42+
print("- Future throwing")
43+
throw HomeworkError.dogAteIt(dogName + " the dog")
44+
}
45+
46+
print("- Future returning normally")
47+
return greeting + "!"
48+
}
49+
50+
// If the intent is not to test suspending, wait a bit so the first task
51+
// can complete.
52+
if !doSuspend {
53+
print("+ Reader sleeping")
54+
sleep(1)
55+
}
56+
57+
do {
58+
print("+ Reader waiting for the result")
59+
let result = try await taskHandle.get()
60+
completed = true
61+
print("+ Normal return: \(result)")
62+
assert(result == "Hello \(name) from async world!")
63+
} catch HomeworkError.dogAteIt(let badDog) {
64+
completed = true
65+
print("+ Error return: HomeworkError.dogAteIt(\(badDog))")
66+
assert(badDog == dogName + " the dog")
67+
} catch {
68+
fatalError("Caught a different exception?")
69+
}
70+
71+
assert(completed)
72+
print("Finished test")
73+
}
74+
75+
76+
@main struct Main {
77+
static func main() async {
78+
await testSimple(name: "Ted", dogName: "Hazel", shouldThrow: false, doSuspend: false)
79+
await testSimple(name: "Ted", dogName: "Hazel", shouldThrow: true, doSuspend: false)
80+
await testSimple(name: "Ted", dogName: "Hazel", shouldThrow: false, doSuspend: true)
81+
await testSimple(name: "Ted", dogName: "Hazel", shouldThrow: true, doSuspend: true)
82+
83+
print("Done")
84+
}
85+
}

0 commit comments

Comments
 (0)