Skip to content

Commit 38bc03a

Browse files
authored
Merge pull request #2276 from lxbndr/fix-nsrecursivelock-w-test
Initialize pthread mutexes in NSRecursiveLock
2 parents 5442503 + d56ad14 commit 38bc03a

File tree

2 files changed

+78
-1
lines changed

2 files changed

+78
-1
lines changed

Foundation/NSLock.swift

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -243,9 +243,14 @@ open class NSRecursiveLock: NSObject, NSLocking {
243243
var attrib = pthread_mutexattr_t()
244244
#endif
245245
withUnsafeMutablePointer(to: &attrib) { attrs in
246+
pthread_mutexattr_init(attrs)
246247
pthread_mutexattr_settype(attrs, Int32(PTHREAD_MUTEX_RECURSIVE))
247248
pthread_mutex_init(mutex, attrs)
248249
}
250+
#if os(macOS) || os(iOS)
251+
pthread_cond_init(timeoutCond, nil)
252+
pthread_mutex_init(timeoutMutex, nil)
253+
#endif
249254
#endif
250255
}
251256

TestFoundation/TestNSLock.swift

Lines changed: 73 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,8 @@ class TestNSLock: XCTestCase {
1313

1414
("test_lockWait", test_lockWait),
1515
("test_threadsAndLocks", test_threadsAndLocks),
16-
16+
("test_recursiveLock", test_recursiveLock),
17+
1718
]
1819
}
1920

@@ -115,4 +116,75 @@ class TestNSLock: XCTestCase {
115116
lock.unlock()
116117
}
117118
}
119+
120+
func test_recursiveLock() {
121+
// We will start 10 threads, and their collective task will be to reduce
122+
// the countdown value to zero in less than 30 seconds.
123+
124+
let threadCount = 10
125+
let countdownPerThread = 1000
126+
let endTime = Date(timeIntervalSinceNow: 30)
127+
128+
var completedThreadCount = 0
129+
var countdownValue = threadCount * countdownPerThread
130+
131+
let threadCompletedCondition = NSCondition()
132+
let countdownValueLock = NSRecursiveLock()
133+
134+
for _ in 0..<threadCount {
135+
let thread = Thread {
136+
137+
// Some dummy work on countdown.
138+
// Add/substract operations are balanced to decrease countdown
139+
// exactly by countdownPerThread
140+
141+
for _ in 0..<countdownPerThread {
142+
countdownValueLock.lock()
143+
countdownValue -= 3
144+
countdownValueLock.lock()
145+
countdownValue += 4
146+
countdownValueLock.unlock()
147+
countdownValueLock.unlock()
148+
149+
countdownValueLock.lock()
150+
countdownValue += 2
151+
countdownValueLock.lock()
152+
countdownValue -= 1
153+
countdownValueLock.unlock()
154+
countdownValue -= 3
155+
countdownValueLock.unlock()
156+
157+
countdownValueLock.lock()
158+
countdownValue += 1
159+
countdownValueLock.unlock()
160+
161+
countdownValueLock.lock()
162+
countdownValue -= 1
163+
countdownValueLock.unlock()
164+
}
165+
166+
threadCompletedCondition.lock()
167+
completedThreadCount += 1
168+
threadCompletedCondition.broadcast()
169+
threadCompletedCondition.unlock()
170+
}
171+
thread.start()
172+
}
173+
174+
threadCompletedCondition.lock()
175+
176+
var isTimedOut = false
177+
while !isTimedOut && completedThreadCount < threadCount {
178+
isTimedOut = !threadCompletedCondition.wait(until: endTime)
179+
}
180+
181+
countdownValueLock.lock()
182+
let resultCountdownValue = countdownValue
183+
countdownValueLock.unlock()
184+
185+
XCTAssertEqual(completedThreadCount, threadCount, "Some threads are still not finished, could be hung")
186+
XCTAssertEqual(resultCountdownValue, 0, "Wrong coundtdown means unresolved race conditions, locks broken")
187+
188+
threadCompletedCondition.unlock()
189+
}
118190
}

0 commit comments

Comments
 (0)