@@ -18,105 +18,109 @@ import Testing
18
18
struct ConcurrencyHelpersTest {
19
19
@Suite
20
20
struct ThreadSafeKeyValueStoreTests {
21
- let queue = DispatchQueue ( label: " ConcurrencyHelpersTest " , attributes: . concurrent)
22
21
23
22
@Test (
24
23
. bug( " https://github.com/swiftlang/swift-package-manager/issues/8770 " ) ,
25
24
)
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 {
30
27
var expected = [ Int: Int] ( )
31
28
let lock = NSLock ( )
32
29
33
30
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
+ }
46
46
}
47
47
}
48
+ try await group. waitForAll ( )
48
49
}
49
50
50
- try #require( sync. wait ( timeout: . now( ) + . seconds( 300 ) ) == . success)
51
51
expected. forEach { key, value in
52
- #expect( cache [ key] == value)
52
+ #expect( cache [ key] == value, " Iteration \( num ) failed " )
53
53
}
54
54
}
55
55
}
56
56
57
57
@Test (
58
58
. bug( " https://github.com/swiftlang/swift-package-manager/issues/8770 " ) ,
59
59
)
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 {
64
62
var expected = [ Int] ( )
65
63
let lock = NSLock ( )
66
64
67
65
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)
74
76
}
75
- cache. append ( value)
76
77
}
78
+ try await group. waitForAll ( )
77
79
}
78
80
79
-
80
- try #require( sync. wait ( timeout: . now( ) + . seconds( 300 ) ) == . success)
81
81
let expectedSorted = expected. sorted ( )
82
82
let resultsSorted = cache. get ( ) . sorted ( )
83
- #expect( expectedSorted == resultsSorted)
83
+ #expect( expectedSorted == resultsSorted, " Iteration \( num ) failed " )
84
84
}
85
85
}
86
86
}
87
87
88
88
@Test (
89
89
. bug( " https://github.com/swiftlang/swift-package-manager/issues/8770 " ) ,
90
90
)
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
+ }
95
105
106
+ for num in 0 ..< 100 {
96
107
var winner : Int ?
97
- let lock = NSLock ( )
108
+ let cache = ThreadSafeBox < Int > ( )
109
+ let coordinator = SerialCoordinator ( )
98
110
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 ) )
100
116
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)
114
119
}
115
120
}
121
+ try await group. waitForAll ( )
116
122
}
117
-
118
- try #require( sync. wait ( timeout: . now( ) + . seconds( 300 ) ) == . success)
119
- #expect( cache. get ( ) == winner)
123
+ #expect( cache. get ( ) == winner, " Iteration \( num) failed " )
120
124
}
121
125
}
122
126
0 commit comments