Skip to content

Commit 1417bbb

Browse files
committed
Add a basic mutex test
1 parent 744e81c commit 1417bbb

File tree

1 file changed

+145
-0
lines changed

1 file changed

+145
-0
lines changed
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
// RUN: %target-run-simple-swift(%import-libdispatch)
2+
// REQUIRES: executable_test
3+
// REQUIRES: libdispatch
4+
// REQUIRES: synchronization
5+
6+
import Synchronization
7+
import StdlibUnittest
8+
import Dispatch
9+
10+
let suite = TestSuite("LockSingleConsumerStackTests")
11+
12+
@available(SwiftStdlib 6.0, *)
13+
class LockSingleConsumerStack<Element> {
14+
struct Node {
15+
let value: Element
16+
var next: UnsafeMutablePointer<Node>?
17+
}
18+
typealias NodePtr = UnsafeMutablePointer<Node>
19+
20+
private let _last = Mutex<NodePtr?>(nil)
21+
private let _consumerCount = Mutex<Int>(0)
22+
private var foo = 0
23+
24+
deinit {
25+
// Discard remaining nodes
26+
while let _ = pop() {}
27+
}
28+
29+
// Push the given element to the top of the stack.
30+
// It is okay to concurrently call this in an arbitrary number of threads.
31+
func push(_ value: Element) {
32+
let new = NodePtr.allocate(capacity: 1)
33+
new.initialize(to: Node(value: value, next: nil))
34+
35+
_last.withLockUnchecked {
36+
new.pointee.next = $0
37+
$0 = new
38+
}
39+
}
40+
41+
// Pop and return the topmost element from the stack.
42+
// This method does not support multiple overlapping concurrent calls.
43+
func pop() -> Element? {
44+
precondition(
45+
_consumerCount.withLock {
46+
let old = $0
47+
$0 += 1
48+
return old == 0
49+
},
50+
"Multiple consumers detected")
51+
52+
defer {
53+
_consumerCount.withLock {
54+
$0 -= 1
55+
}
56+
}
57+
58+
return _last.withLock { (c: inout NodePtr?) -> Element? in
59+
guard let current = c else {
60+
return nil
61+
}
62+
63+
c = current.pointee.next
64+
65+
let result = current.move()
66+
current.deallocate()
67+
return result.value
68+
}
69+
}
70+
}
71+
72+
if #available(SwiftStdlib 6.0, *) {
73+
74+
suite.test("basics") {
75+
let stack = LockSingleConsumerStack<Int>()
76+
expectNil(stack.pop())
77+
stack.push(0)
78+
expectEqual(0, stack.pop())
79+
80+
stack.push(1)
81+
stack.push(2)
82+
stack.push(3)
83+
stack.push(4)
84+
expectEqual(4, stack.pop())
85+
expectEqual(3, stack.pop())
86+
expectEqual(2, stack.pop())
87+
expectEqual(1, stack.pop())
88+
expectNil(stack.pop())
89+
}
90+
91+
suite.test("concurrentPushes") {
92+
let stack = LockSingleConsumerStack<(thread: Int, value: Int)>()
93+
94+
let numThreads = 100
95+
let numValues = 10_000
96+
DispatchQueue.concurrentPerform(iterations: numThreads) { thread in
97+
for value in 1 ... numValues {
98+
stack.push((thread: thread, value: value))
99+
}
100+
}
101+
102+
var expected: [Int] = Array(repeating: numValues, count: numThreads)
103+
while let (thread, value) = stack.pop() {
104+
expectEqual(expected[thread], value)
105+
expected[thread] -= 1
106+
}
107+
expectEqual(Array(repeating: 0, count: numThreads), expected)
108+
}
109+
110+
suite.test("concurrentPushesAndPops") {
111+
let stack = LockSingleConsumerStack<(thread: Int, value: Int)>()
112+
113+
let numThreads = 100
114+
let numValues = 10_000
115+
116+
var perThreadSums: [Int] = Array(repeating: 0, count: numThreads)
117+
let consumerQueue = DispatchQueue(label: "org.swift.background")
118+
consumerQueue.async {
119+
var count = 0
120+
while count < numThreads * numValues {
121+
// Note: busy wait
122+
if let (thread, value) = stack.pop() {
123+
perThreadSums[thread] += value
124+
count += 1
125+
}
126+
}
127+
}
128+
129+
DispatchQueue.concurrentPerform(iterations: numThreads + 1) { thread in
130+
if thread < numThreads {
131+
// Producers
132+
for value in 0 ..< numValues {
133+
stack.push((thread: thread, value: value))
134+
}
135+
}
136+
}
137+
138+
consumerQueue.sync {
139+
expectEqual(Array(repeating: numValues * (numValues - 1) / 2, count: numThreads), perThreadSums)
140+
}
141+
}
142+
143+
} // if #available(SwiftStdlib 6.0)
144+
145+
runAllTests()

0 commit comments

Comments
 (0)