Skip to content

Commit 0cef64a

Browse files
authored
Tests: make threadSafe*() tests more robust (#9045)
1 parent 2669021 commit 0cef64a

File tree

1 file changed

+59
-55
lines changed

1 file changed

+59
-55
lines changed

Tests/BasicsTests/ConcurrencyHelpersTests.swift

Lines changed: 59 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -18,105 +18,109 @@ import Testing
1818
struct ConcurrencyHelpersTest {
1919
@Suite
2020
struct ThreadSafeKeyValueStoreTests {
21-
let queue = DispatchQueue(label: "ConcurrencyHelpersTest", attributes: .concurrent)
2221

2322
@Test(
2423
.bug("https://github.com/swiftlang/swift-package-manager/issues/8770"),
2524
)
26-
func threadSafeKeyValueStore() throws {
27-
for _ in 0 ..< 100 {
28-
let sync = DispatchGroup()
29-
25+
func threadSafeKeyValueStore() async throws {
26+
for num in 0 ..< 100 {
3027
var expected = [Int: Int]()
3128
let lock = NSLock()
3229

3330
let cache = ThreadSafeKeyValueStore<Int, Int>()
34-
for index in 0 ..< 1000 {
35-
self.queue.async(group: sync) {
36-
Thread.sleep(forTimeInterval: Double.random(in: 100 ... 300) * 1.0e-6)
37-
let value = Int.random(in: Int.min ..< Int.max)
38-
lock.withLock {
39-
expected[index] = value
40-
}
41-
cache.memoize(index) {
42-
value
43-
}
44-
cache.memoize(index) {
45-
Int.random(in: Int.min ..< Int.max)
31+
32+
try await withThrowingTaskGroup(of: Void.self) { group in
33+
for index in 0 ..< 1000 {
34+
group.addTask {
35+
try await Task.sleep(nanoseconds: UInt64(Double.random(in: 100 ... 300) * 1000))
36+
let value = Int.random(in: Int.min ..< Int.max)
37+
lock.withLock {
38+
expected[index] = value
39+
}
40+
cache.memoize(index) {
41+
value
42+
}
43+
cache.memoize(index) {
44+
Int.random(in: Int.min ..< Int.max)
45+
}
4646
}
4747
}
48+
try await group.waitForAll()
4849
}
4950

50-
try #require(sync.wait(timeout: .now() + .seconds(300)) == .success)
5151
expected.forEach { key, value in
52-
#expect(cache[key] == value)
52+
#expect(cache[key] == value, "Iteration \(num) failed")
5353
}
5454
}
5555
}
5656

5757
@Test(
5858
.bug("https://github.com/swiftlang/swift-package-manager/issues/8770"),
5959
)
60-
func threadSafeArrayStore() throws {
61-
for _ in 0 ..< 100 {
62-
let sync = DispatchGroup()
63-
60+
func threadSafeArrayStore() async throws {
61+
for num in 0 ..< 100 {
6462
var expected = [Int]()
6563
let lock = NSLock()
6664

6765
let cache = ThreadSafeArrayStore<Int>()
68-
for _ in 0 ..< 1000 {
69-
self.queue.async(group: sync) {
70-
Thread.sleep(forTimeInterval: Double.random(in: 100 ... 300) * 1.0e-6)
71-
let value = Int.random(in: Int.min ..< Int.max)
72-
lock.withLock {
73-
expected.append(value)
66+
67+
try await withThrowingTaskGroup(of: Void.self) { group in
68+
for _ in 0 ..< 1000 {
69+
group.addTask {
70+
try await Task.sleep(nanoseconds: UInt64(Double.random(in: 100 ... 300) * 1000))
71+
let value = Int.random(in: Int.min ..< Int.max)
72+
lock.withLock {
73+
expected.append(value)
74+
}
75+
cache.append(value)
7476
}
75-
cache.append(value)
7677
}
78+
try await group.waitForAll()
7779
}
7880

79-
80-
try #require(sync.wait(timeout: .now() + .seconds(300)) == .success)
8181
let expectedSorted = expected.sorted()
8282
let resultsSorted = cache.get().sorted()
83-
#expect(expectedSorted == resultsSorted)
83+
#expect(expectedSorted == resultsSorted, "Iteration \(num) failed")
8484
}
8585
}
8686
}
8787

8888
@Test(
8989
.bug("https://github.com/swiftlang/swift-package-manager/issues/8770"),
9090
)
91-
func threadSafeBox() throws {
92-
let queue = DispatchQueue(label: "ConcurrencyHelpersTest", attributes: .concurrent)
93-
for _ in 0 ..< 100 {
94-
let sync = DispatchGroup()
91+
func threadSafeBox() async throws {
92+
// Actor to serialize the critical section that was previously handled by the serial queue
93+
actor SerialCoordinator {
94+
func processTask(_ index: Int, winner: inout Int?, cache: ThreadSafeBox<Int>) {
95+
// This simulates the serial queue behavior - both winner determination
96+
// and cache memoization happen atomically in the same serial context
97+
if winner == nil {
98+
winner = index
99+
}
100+
cache.memoize {
101+
index
102+
}
103+
}
104+
}
95105

106+
for num in 0 ..< 100 {
96107
var winner: Int?
97-
let lock = NSLock()
108+
let cache = ThreadSafeBox<Int>()
109+
let coordinator = SerialCoordinator()
98110

99-
let serial = DispatchQueue(label: "testThreadSafeBoxSerial")
111+
try await withThrowingTaskGroup(of: Void.self) { group in
112+
for index in 0 ..< 1000 {
113+
group.addTask {
114+
// Random sleep to simulate concurrent access timing
115+
try await Task.sleep(nanoseconds: UInt64(Double.random(in: 100 ... 300) * 1000))
100116

101-
let cache = ThreadSafeBox<Int>()
102-
for index in 0 ..< 1000 {
103-
queue.async(group: sync) {
104-
Thread.sleep(forTimeInterval: Double.random(in: 100 ... 300) * 1.0e-6)
105-
serial.async(group: sync) {
106-
lock.withLock {
107-
if winner == nil {
108-
winner = index
109-
}
110-
}
111-
cache.memoize {
112-
index
113-
}
117+
// Process both winner determination and cache memoization serially
118+
await coordinator.processTask(index, winner: &winner, cache: cache)
114119
}
115120
}
121+
try await group.waitForAll()
116122
}
117-
118-
try #require(sync.wait(timeout: .now() + .seconds(300)) == .success)
119-
#expect(cache.get() == winner)
123+
#expect(cache.get() == winner, "Iteration \(num) failed")
120124
}
121125
}
122126

0 commit comments

Comments
 (0)