Skip to content

Commit 58056ff

Browse files
author
Julian Lettner
committed
Add positive TSan tests for Swift Concurrency
Add tests that check that TSan detects true positives in simple examples for both actors and async tasks.
1 parent ea5bb77 commit 58056ff

File tree

2 files changed

+132
-0
lines changed

2 files changed

+132
-0
lines changed
Lines changed: 67 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,67 @@
1+
// RUN: %target-swiftc_driver %s -Xfrontend -enable-experimental-concurrency -parse-as-library %import-libdispatch -target %sanitizers-target-triple -g -sanitize=thread -o %t
2+
// RUN: %target-codesign %t
3+
// RUN: env %env-TSAN_OPTIONS="abort_on_error=0" not %target-run %t 2>&1 | %FileCheck %s
4+
5+
// REQUIRES: executable_test
6+
// REQUIRES: concurrency
7+
// REQUIRES: libdispatch
8+
// REQUIRES: tsan_runtime
9+
10+
var globalCounterValue = 0
11+
12+
actor Counter {
13+
func next() -> Int {
14+
let current = globalCounterValue
15+
globalCounterValue += 1
16+
return current
17+
}
18+
}
19+
20+
func worker(identity: Int, counters: [Counter], numIterations: Int) async {
21+
for _ in 0..<numIterations {
22+
let counterIndex = Int.random(in: 0 ..< counters.count)
23+
let counter = counters[counterIndex]
24+
let nextValue = await counter.next()
25+
print("Worker \(identity) calling counter \(counterIndex) produced \(nextValue)")
26+
}
27+
}
28+
29+
func runTest(numCounters: Int, numWorkers: Int, numIterations: Int) async {
30+
// Create counter actors.
31+
var counters: [Counter] = []
32+
for _ in 0..<numCounters {
33+
counters.append(Counter())
34+
}
35+
36+
// Create a bunch of worker threads.
37+
var workers: [Task.Handle<Void, Error>] = []
38+
for i in 0..<numWorkers {
39+
workers.append(
40+
Task.runDetached { [counters] in
41+
await worker(identity: i, counters: counters, numIterations: numIterations)
42+
}
43+
)
44+
}
45+
46+
// Wait until all of the workers have finished.
47+
for worker in workers {
48+
try! await worker.get()
49+
}
50+
51+
print("DONE!")
52+
}
53+
54+
@main struct Main {
55+
static func main() async {
56+
// Useful for debugging: specify counter/worker/iteration counts
57+
let args = CommandLine.arguments
58+
let counters = args.count >= 2 ? Int(args[1])! : 10
59+
let workers = args.count >= 3 ? Int(args[2])! : 10
60+
let iterations = args.count >= 4 ? Int(args[3])! : 100
61+
print("counters: \(counters), workers: \(workers), iterations: \(iterations)")
62+
await runTest(numCounters: counters, numWorkers: workers, numIterations: iterations)
63+
}
64+
}
65+
66+
// CHECK: ThreadSanitizer: {{(Swift access|data)}} race
67+
// CHECK: Location is global 'globalCounterValue'
Lines changed: 65 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,65 @@
1+
// RUN: %target-swiftc_driver %s -Xfrontend -enable-experimental-concurrency -parse-as-library %import-libdispatch -target %sanitizers-target-triple -g -sanitize=thread -o %t
2+
// RUN: %target-codesign %t
3+
// RUN: env %env-TSAN_OPTIONS="abort_on_error=0" not %target-run %t 2>&1 | %FileCheck %s
4+
5+
// REQUIRES: executable_test
6+
// REQUIRES: concurrency
7+
// REQUIRES: libdispatch
8+
// REQUIRES: tsan_runtime
9+
10+
#if canImport(Darwin)
11+
import Darwin
12+
#elseif canImport(Glibc)
13+
import Glibc
14+
#endif
15+
16+
func fib(_ n: Int) -> Int {
17+
var first = 0
18+
var second = 1
19+
for _ in 0..<n {
20+
let temp = first
21+
first = second
22+
second = temp + first
23+
}
24+
return first
25+
}
26+
27+
var racyCounter = 0
28+
29+
func asyncFib(_ n: Int) async -> Int {
30+
racyCounter += 1
31+
if n == 0 || n == 1 {
32+
return n
33+
}
34+
35+
async let first = await asyncFib(n-2)
36+
async let second = await asyncFib(n-1)
37+
38+
// Sleep a random amount of time waiting on the result producing a result.
39+
usleep(UInt32.random(in: 0..<100) * 1000)
40+
41+
let result = await first + second
42+
43+
// Sleep a random amount of time before producing a result.
44+
usleep(UInt32.random(in: 0..<100) * 1000)
45+
46+
return result
47+
}
48+
49+
func runFibonacci(_ n: Int) async {
50+
let result = await asyncFib(n)
51+
52+
print()
53+
print("Async fib = \(result), sequential fib = \(fib(n))")
54+
assert(result == fib(n))
55+
print("asyncFib() called around \(racyCounter) times")
56+
}
57+
58+
@main struct Main {
59+
static func main() async {
60+
await runFibonacci(10)
61+
}
62+
}
63+
64+
// CHECK: ThreadSanitizer: Swift access race
65+
// CHECK: Location is global 'racyCounter'

0 commit comments

Comments
 (0)