Merge pull request #1193 from spevans/pr_nslock
diff --git a/Docs/Status.md b/Docs/Status.md
index fa06c03..7a58b1d 100644
--- a/Docs/Status.md
+++ b/Docs/Status.md
@@ -47,7 +47,7 @@
|------------------------------|-----------------|---------------|--------------------------------------------------------------------------------------------------------------------|
| `URLAuthenticationChallenge` | Unimplemented | None | |
| `URLCache` | Unimplemented | None | |
- | `URLCredential` | Complete | Incomplete | |
+ | `URLCredential` | Complete | Incomplete | |
| `URLCredentialStorage` | Unimplemented | None | |
| `NSURLError*` | Complete | N/A | |
| `URLProtectionSpace` | Unimplemented | None | |
@@ -96,13 +96,13 @@
| `DateComponentFormatter` | Unimplemented | None | |
| `DateIntervalFormatter` | Unimplemented | None | |
| `EnergyFormatter` | Unimplemented | None | |
- | `ISO8601DateFormatter` | Unimplemented | None | |
- | `LengthFormatter` | Complete | Substantial | |
+ | `ISO8601DateFormatter` | Unimplemented | None | |
+ | `LengthFormatter` | Complete | Substantial | |
| `MassFormatter` | Complete | Substantial | Needs localization |
| `NumberFormatter` | Mostly Complete | Substantial | `objectValue(_:range:)` remains unimplemented |
| `PersonNameComponentsFormatter` | Unimplemented | None | |
| `ByteCountFormatter` | Mostly Complete | Substantial | `init?(coder:)` remains unimplemented |
- | `DateFormatter` | Mostly Complete | Incomplete | `objectValue(_:range:)` remain unimplemented |
+ | `DateFormatter` | Mostly Complete | Incomplete | `objectValue(_:range:)` remain unimplemented |
| `Formatter` | Complete | N/A | |
| `MeasurementFormatter` | Unimplemented | None | |
@@ -123,8 +123,8 @@
| Entity Name | Status | Test Coverage | Notes |
|-----------------------------|-----------------|---------------|-------------------------------------------------------------------------------|
- | `NSJSONSerialization` | Mostly Complete | Substantial | `jsonObject(with:options:)` with streams remains unimplemented |
- | `NSKeyedArchiver` | Complete | Substantial | |
+ | `NSJSONSerialization` | Mostly Complete | Substantial | `jsonObject(with:options:)` with streams remains unimplemented |
+ | `NSKeyedArchiver` | Complete | Substantial | |
| `NSKeyedCoderOldStyleArray` | N/A | N/A | For internal use only |
| `NSKeyedUnarchiver` | Mostly Complete | Substantial | `decodingFailurePolicy.set` remains unimplemented |
| `NSKeyedArchiverHelpers` | N/A | N/A | For internal use only |
@@ -152,7 +152,7 @@
| Entity Name | Status | Test Coverage | Notes |
|-----------------------|-----------------|---------------|---------------------------------------------------------------------------------------------------------------------------------------------------------------------|
- | `NSOrderedSet` | Mostly Complete | Substantial | `NS[Mutable]Copying`, and `array` & `set` (and associated indexing methods) remain unimplemented |
+ | `NSOrderedSet` | Mostly Complete | Substantial | `NS[Mutable]Copying`, and `array` & `set` (and associated indexing methods) remain unimplemented |
| `NSMutableOrderedSet` | Mostly Complete | Substantial | `NSCoding` and `sortRange(_:options:, usingComparator:)` with non-empty options remain unimplemented |
| `NSCFArray` | N/A | N/A | For internal use only |
| `NSIndexSet` | Mostly Complete | Incomplete | `NSCoding`, `NSCopying`, and concurrent `enumerateWithOptions(_:range:paramType:returnType:block:)` remain unimplemented |
@@ -197,10 +197,10 @@
| Entity Name | Status | Test Coverage | Notes |
|-----------------------------|-----------------|---------------|------------------------------------------------------------------------------------------------------------------------------------------------------------------|
- | `NSRegularExpression` | Complete | Substantial | |
+ | `NSRegularExpression` | Complete | Substantial | |
| `Scanner` | Mostly Complete | Incomplete | `scanHex<T: _FloatLike>(_:locale:locationToScanFrom:to:)` and `localizedScannerWithString(_:)` remain unimplemented |
| `NSTextCheckingResult` | Mostly Complete | Incomplete | `NSCoding`, `NSCopying`, `resultType`, and `range(at:)` remain unimplemented |
- | `NSAttributedString` | Incomplete | Incomplete | `NSCoding`, `NS[Mutable]Copying`, `attributedSubstring(from:)`, `isEqual(to:)`, `init(NSAttributedString:)` remain unimplemented |
+ | `NSAttributedString` | Incomplete | Incomplete | `NSCoding`, `NS[Mutable]Copying`, `attributedSubstring(from:)`, `isEqual(to:)`, `init(NSAttributedString:)` remain unimplemented |
| `NSMutableAttributedString` | Unimplemented | Incomplete | Only `addAttribute(_:value:range:)` is implemented |
| `NSCharacterSet` | Mostly Complete | Incomplete | `NSCoding` remains unimplemented |
| `NSMutableCharacterSet` | Mostly Complete | None | Decoding remains unimplemented |
@@ -216,14 +216,14 @@
| Entity Name | Status | Test Coverage | Notes |
|-----------------------------------|-----------------|---------------|-------------------------------------------------------------------------------|
- | `NSRange` | Complete | Incomplete | |
+ | `NSRange` | Complete | Incomplete | |
| `Decimal` | Unimplemented | None | |
| `NSDecimalNumber` | Unimplemented | None | |
| `NSDecimalNumberHandler` | Unimplemented | None | |
| `CGPoint` | Complete | Substantial | |
| `CGSize` | Complete | Substantial | |
| `CGRect` | Complete | Substantial | |
- | `NSEdgeInsets` | Mostly Complete | None | |
+ | `NSEdgeInsets` | Mostly Complete | None | |
| `NSGeometry` | Mostly Complete | Substantial | `NSIntegralRectWithOptions` `.AlignRectFlipped` support remains unimplemented |
| `CGFloat` | Complete | Substantial | |
| `AffineTransform` | Complete | None | |
@@ -265,7 +265,7 @@
| Entity Name | Statues | Test Coverage | Notes |
|----------------|-----------------|---------------|-------------------------------------------------------------------------------------------------------------------------------|
| `UserDefaults` | Incomplete | None | `dictionaryRepresentation()`, domain support, and forced objects remain unimplemented. Unit tests are currently commented out |
- | `NSLocale` | Complete | Incomplete | Only unit test asserts locale key constant names |
+ | `NSLocale` | Complete | Incomplete | Only unit test asserts locale key constant names |
| `Locale` | Complete | Incomplete | Only unit test asserts value copying |
* **OS**: Mechanisms for interacting with the operating system on a file system level as well as process and thread level
@@ -282,24 +282,24 @@
| `Operation` | Complete | Incomplete | |
| `BlockOperation` | Complete | Incomplete | |
| `OperationQueue` | Complete | Incomplete | |
- | `Lock` | Mostly Complete | None | `lock(before:)` remains unimplemented |
+ | `Lock` | Complete | Incomplete | |
| `ConditionLock` | Complete | None | |
- | `RecursiveLock` | Mostly Complete | None | `lock(before:)` remains unimplemented |
- | `Condition` | Complete | None | |
+ | `RecursiveLock` | Complete | None | |
+ | `Condition` | Complete | Incomplete | |
* **DateTime**: Classes for representing dates, timezones, and calendars.
| Entity Name | Status | Test Coverage | Notes |
|--------------------|-----------------|---------------|---------------------------------------------------------------------------------------------------------------------------------|
- | `NSCalendar` | Complete | None | `autoupdatingCurrent`, and `enumerateDates` remain unimplemented |
- | `NSDateComponents` | Complete | None | |
+ | `NSCalendar` | Complete | None | `autoupdatingCurrent`, and `enumerateDates` remain unimplemented |
+ | `NSDateComponents` | Complete | None | |
| `Calendar` | Complete | Incomplete | |
| `DateComponents` | Complete | Incomplete | |
- | `NSDate` | Complete | Incomplete | |
+ | `NSDate` | Complete | Incomplete | |
| `NSDateInterval` | Complete | None | |
| `DateInterval` | Complete | None | |
| `Date` | Complete | Incomplete | |
- | `NSTimeZone` | Mostly Complete | Incomplete | `local`, `timeZoneDataVersion` and setting `abbreviationDictionary` remain unimplemented |
+ | `NSTimeZone` | Mostly Complete | Incomplete | `local`, `timeZoneDataVersion` and setting `abbreviationDictionary` remain unimplemented |
| `TimeZone` | Complete | Incomplete | |
* **Notifications**: Classes for loosely coupling events from a set of many observers.
diff --git a/Foundation.xcodeproj/project.pbxproj b/Foundation.xcodeproj/project.pbxproj
index eeace9e..4fff062 100644
--- a/Foundation.xcodeproj/project.pbxproj
+++ b/Foundation.xcodeproj/project.pbxproj
@@ -326,6 +326,7 @@
B910957B1EEF237800A71930 /* NSString-UTF16-BE-data.txt in Resources */ = {isa = PBXBuildFile; fileRef = B91095791EEF237800A71930 /* NSString-UTF16-BE-data.txt */; };
B933A79E1F3055F700FE6846 /* NSString-UTF32-BE-data.txt in Resources */ = {isa = PBXBuildFile; fileRef = B933A79C1F3055F600FE6846 /* NSString-UTF32-BE-data.txt */; };
B933A79F1F3055F700FE6846 /* NSString-UTF32-LE-data.txt in Resources */ = {isa = PBXBuildFile; fileRef = B933A79D1F3055F600FE6846 /* NSString-UTF32-LE-data.txt */; };
+ B951B5EC1F4E2A2000D8B332 /* TestNSLock.swift in Sources */ = {isa = PBXBuildFile; fileRef = B951B5EB1F4E2A2000D8B332 /* TestNSLock.swift */; };
B9974B961EDF4A22007F15B8 /* TransferState.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9974B8F1EDF4A22007F15B8 /* TransferState.swift */; };
B9974B971EDF4A22007F15B8 /* MultiHandle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9974B901EDF4A22007F15B8 /* MultiHandle.swift */; };
B9974B981EDF4A22007F15B8 /* libcurlHelpers.swift in Sources */ = {isa = PBXBuildFile; fileRef = B9974B911EDF4A22007F15B8 /* libcurlHelpers.swift */; };
@@ -795,6 +796,7 @@
B91095791EEF237800A71930 /* NSString-UTF16-BE-data.txt */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text; path = "NSString-UTF16-BE-data.txt"; sourceTree = "<group>"; };
B933A79C1F3055F600FE6846 /* NSString-UTF32-BE-data.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "NSString-UTF32-BE-data.txt"; sourceTree = "<group>"; };
B933A79D1F3055F600FE6846 /* NSString-UTF32-LE-data.txt */ = {isa = PBXFileReference; lastKnownFileType = text; path = "NSString-UTF32-LE-data.txt"; sourceTree = "<group>"; };
+ B951B5EB1F4E2A2000D8B332 /* TestNSLock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TestNSLock.swift; sourceTree = "<group>"; };
B9974B8F1EDF4A22007F15B8 /* TransferState.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = TransferState.swift; path = http/TransferState.swift; sourceTree = "<group>"; };
B9974B901EDF4A22007F15B8 /* MultiHandle.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = MultiHandle.swift; path = http/MultiHandle.swift; sourceTree = "<group>"; };
B9974B911EDF4A22007F15B8 /* libcurlHelpers.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; name = libcurlHelpers.swift; path = http/libcurlHelpers.swift; sourceTree = "<group>"; };
@@ -1498,6 +1500,7 @@
400E22641C1A4E58007C5933 /* TestProcessInfo.swift */,
EA01AAEB1DA839C4008F4E07 /* TestProgress.swift */,
5E5835F31C20C9B500C81317 /* TestThread.swift */,
+ B951B5EB1F4E2A2000D8B332 /* TestNSLock.swift */,
CC5249BF1D341D23007CB54D /* TestUnitConverter.swift */,
D4FE895A1D703D1100DA7986 /* TestURLRequest.swift */,
5B6F17961C48631C00935030 /* TestUtils.swift */,
@@ -2445,6 +2448,7 @@
D512D17C1CD883F00032E6A5 /* TestFileHandle.swift in Sources */,
D4FE895B1D703D1100DA7986 /* TestURLRequest.swift in Sources */,
684C79011F62B611005BD73E /* TestNSNumberBridging.swift in Sources */,
+ B951B5EC1F4E2A2000D8B332 /* TestNSLock.swift in Sources */,
5B13B33A1C582D4C00651CE2 /* TestNSNumber.swift in Sources */,
5B13B3521C582D4C00651CE2 /* TestNSValue.swift in Sources */,
5B13B3311C582D4C00651CE2 /* TestIndexPath.swift in Sources */,
diff --git a/Foundation/NSLock.swift b/Foundation/NSLock.swift
index f4b41d0..886b5ba 100644
--- a/Foundation/NSLock.swift
+++ b/Foundation/NSLock.swift
@@ -1,6 +1,6 @@
// This source file is part of the Swift.org open source project
//
-// Copyright (c) 2014 - 2016 Apple Inc. and the Swift project authors
+// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
// Licensed under Apache License v2.0 with Runtime Library Exception
//
// See http://swift.org/LICENSE.txt for license information
@@ -21,39 +21,71 @@
func unlock()
}
-open class NSLock: NSObject, NSLocking {
#if CYGWIN
- internal var mutex = UnsafeMutablePointer<pthread_mutex_t?>.allocate(capacity: 1)
+private typealias _PthreadMutexPointer = UnsafeMutablePointer<pthread_mutex_t?>
+private typealias _PthreadCondPointer = UnsafeMutablePointer<pthread_cond_t?>
#else
- internal var mutex = UnsafeMutablePointer<pthread_mutex_t>.allocate(capacity: 1)
+private typealias _PthreadMutexPointer = UnsafeMutablePointer<pthread_mutex_t>
+private typealias _PthreadCondPointer = UnsafeMutablePointer<pthread_cond_t>
#endif
-
+
+open class NSLock: NSObject, NSLocking {
+ internal var mutex = _PthreadMutexPointer.allocate(capacity: 1)
+#if os(OSX) || os(iOS)
+ private var timeoutCond = _PthreadCondPointer.allocate(capacity: 1)
+ private var timeoutMutex = _PthreadMutexPointer.allocate(capacity: 1)
+#endif
+
public override init() {
pthread_mutex_init(mutex, nil)
+#if os(OSX) || os(iOS)
+ pthread_cond_init(timeoutCond, nil)
+ pthread_mutex_init(timeoutMutex, nil)
+#endif
}
deinit {
pthread_mutex_destroy(mutex)
mutex.deinitialize()
mutex.deallocate(capacity: 1)
+#if os(OSX) || os(iOS)
+ deallocateTimedLockData(cond: timeoutCond, mutex: timeoutMutex)
+#endif
}
open func lock() {
pthread_mutex_lock(mutex)
}
-
+
open func unlock() {
pthread_mutex_unlock(mutex)
+#if os(OSX) || os(iOS)
+ // Wakeup any threads waiting in lock(before:)
+ pthread_mutex_lock(timeoutMutex)
+ pthread_cond_broadcast(timeoutCond)
+ pthread_mutex_unlock(timeoutMutex)
+#endif
}
-
+
open func `try`() -> Bool {
return pthread_mutex_trylock(mutex) == 0
}
- open func lock(before limit: Date) {
- NSUnimplemented()
+ open func lock(before limit: Date) -> Bool {
+ if pthread_mutex_trylock(mutex) == 0 {
+ return true
+ }
+
+#if os(OSX) || os(iOS)
+ return timedLock(mutex: mutex, endTime: limit, using: timeoutCond, with: timeoutMutex)
+#else
+ guard var endTime = timeSpecFrom(date: limit) else {
+ return false
+ }
+ return pthread_mutex_timedlock(mutex, &endTime) == 0
+#endif
}
-
+
open var name: String?
}
@@ -143,12 +175,12 @@
}
open class NSRecursiveLock: NSObject, NSLocking {
-#if CYGWIN
- internal var mutex = UnsafeMutablePointer<pthread_mutex_t?>.allocate(capacity: 1)
-#else
- internal var mutex = UnsafeMutablePointer<pthread_mutex_t>.allocate(capacity: 1)
+ internal var mutex = _PthreadMutexPointer.allocate(capacity: 1)
+#if os(OSX) || os(iOS)
+ private var timeoutCond = _PthreadCondPointer.allocate(capacity: 1)
+ private var timeoutMutex = _PthreadMutexPointer.allocate(capacity: 1)
#endif
-
+
public override init() {
super.init()
#if CYGWIN
@@ -166,6 +198,9 @@
pthread_mutex_destroy(mutex)
mutex.deinitialize()
mutex.deallocate(capacity: 1)
+#if os(OSX) || os(iOS)
+ deallocateTimedLockData(cond: timeoutCond, mutex: timeoutMutex)
+#endif
}
open func lock() {
@@ -174,28 +209,40 @@
open func unlock() {
pthread_mutex_unlock(mutex)
+#if os(OSX) || os(iOS)
+ // Wakeup any threads waiting in lock(before:)
+ pthread_mutex_lock(timeoutMutex)
+ pthread_cond_broadcast(timeoutCond)
+ pthread_mutex_unlock(timeoutMutex)
+#endif
}
open func `try`() -> Bool {
return pthread_mutex_trylock(mutex) == 0
}
- open func lock(before limit: Date) {
- NSUnimplemented()
+ open func lock(before limit: Date) -> Bool {
+ if pthread_mutex_trylock(mutex) == 0 {
+ return true
+ }
+
+#if os(OSX) || os(iOS)
+ return timedLock(mutex: mutex, endTime: limit, using: timeoutCond, with: timeoutMutex)
+#else
+ guard var endTime = timeSpecFrom(date: limit) else {
+ return false
+ }
+ return pthread_mutex_timedlock(mutex, &endTime) == 0
+#endif
}
open var name: String?
}
open class NSCondition: NSObject, NSLocking {
-#if CYGWIN
- internal var mutex = UnsafeMutablePointer<pthread_mutex_t?>.allocate(capacity: 1)
- internal var cond = UnsafeMutablePointer<pthread_cond_t?>.allocate(capacity: 1)
-#else
- internal var mutex = UnsafeMutablePointer<pthread_mutex_t>.allocate(capacity: 1)
- internal var cond = UnsafeMutablePointer<pthread_cond_t>.allocate(capacity: 1)
-#endif
-
+ internal var mutex = _PthreadMutexPointer.allocate(capacity: 1)
+ internal var cond = _PthreadCondPointer.allocate(capacity: 1)
+
public override init() {
pthread_mutex_init(mutex, nil)
pthread_cond_init(cond, nil)
@@ -221,31 +268,12 @@
open func wait() {
pthread_cond_wait(cond, mutex)
}
-
+
open func wait(until limit: Date) -> Bool {
- let lim = limit.timeIntervalSinceReferenceDate
- let ti = lim - CFAbsoluteTimeGetCurrent()
- if ti < 0.0 {
+ guard var timeout = timeSpecFrom(date: limit) else {
return false
}
- var ts = timespec()
- ts.tv_sec = Int(floor(ti))
- ts.tv_nsec = Int((ti - Double(ts.tv_sec)) * 1_000_000_000.0)
- var tv = timeval()
- withUnsafeMutablePointer(to: &tv) { t in
- gettimeofday(t, nil)
- ts.tv_sec += t.pointee.tv_sec
- ts.tv_nsec += Int(t.pointee.tv_usec) * 1000
- if ts.tv_nsec >= 1_000_000_000 {
- ts.tv_sec += ts.tv_nsec / 1_000_000_000
- ts.tv_nsec = ts.tv_nsec % 1_000_000_000
- }
- }
- let retVal: Int32 = withUnsafePointer(to: &ts) { t in
- return pthread_cond_timedwait(cond, mutex, t)
- }
-
- return retVal == 0
+ return pthread_cond_timedwait(cond, mutex, &timeout) == 0
}
open func signal() {
@@ -258,3 +286,58 @@
open var name: String?
}
+
+private func timeSpecFrom(date: Date) -> timespec? {
+ guard date.timeIntervalSinceNow > 0 else {
+ return nil
+ }
+ let nsecPerSec: Int64 = 1_000_000_000
+ let interval = date.timeIntervalSince1970
+ let intervalNS = Int64(interval * Double(nsecPerSec))
+
+ return timespec(tv_sec: Int(intervalNS / nsecPerSec),
+ tv_nsec: Int(intervalNS % nsecPerSec))
+}
+
+#if os(OSX) || os(iOS)
+
+private func deallocateTimedLockData(cond: _PthreadCondPointer, mutex: _PthreadMutexPointer) {
+ pthread_cond_destroy(cond)
+ cond.deinitialize()
+ cond.deallocate(capacity: 1)
+
+ pthread_mutex_destroy(mutex)
+ mutex.deinitialize()
+ mutex.deallocate(capacity: 1)
+}
+
+// Emulate pthread_mutex_timedlock using pthread_cond_timedwait.
+// lock(before:) passes a condition variable/mutex pair to use.
+// unlock() will use pthread_cond_broadcast() to wake any waits in progress.
+private func timedLock(mutex: _PthreadMutexPointer, endTime: Date,
+ using timeoutCond: _PthreadCondPointer,
+ with timeoutMutex: _PthreadMutexPointer) -> Bool {
+
+ var timeSpec = timeSpecFrom(date: endTime)
+ while var ts = timeSpec {
+ let lockval = pthread_mutex_lock(timeoutMutex)
+ precondition(lockval == 0)
+ let waitval = pthread_cond_timedwait(timeoutCond, timeoutMutex, &ts)
+ precondition(waitval == 0 || waitval == ETIMEDOUT)
+ let unlockval = pthread_mutex_unlock(timeoutMutex)
+ precondition(unlockval == 0)
+
+ if waitval == ETIMEDOUT {
+ return false
+ }
+ let tryval = pthread_mutex_trylock(mutex)
+ precondition(tryval == 0 || tryval == EBUSY)
+ if tryval == 0 { // The lock was obtained.
+ return true
+ }
+ // pthread_cond_timedwait didnt timeout so wait some more.
+ timeSpec = timeSpecFrom(date: endTime)
+ }
+ return false
+}
+#endif
diff --git a/TestFoundation/TestNSLock.swift b/TestFoundation/TestNSLock.swift
new file mode 100644
index 0000000..9191ade
--- /dev/null
+++ b/TestFoundation/TestNSLock.swift
@@ -0,0 +1,125 @@
+// This source file is part of the Swift.org open source project
+//
+// Copyright (c) 2014 - 2017 Apple Inc. and the Swift project authors
+// Licensed under Apache License v2.0 with Runtime Library Exception
+//
+// See http://swift.org/LICENSE.txt for license information
+// See http://swift.org/CONTRIBUTORS.txt for the list of Swift project authors
+//
+
+#if DEPLOYMENT_RUNTIME_OBJC || os(Linux)
+import Foundation
+import XCTest
+#else
+import SwiftFoundation
+import SwiftXCTest
+#endif
+
+import CoreFoundation
+
+class TestNSLock: XCTestCase {
+ static var allTests: [(String, (TestNSLock) -> () throws -> Void)] {
+ return [
+
+ ("test_lockWait", test_lockWait),
+ ("test_threadsAndLocks", test_threadsAndLocks),
+
+ ]
+ }
+
+
+ func test_lockWait() {
+ let condition = NSCondition()
+ let lock = NSLock()
+
+ func test(waitTime: CFTimeInterval, shouldLock: Bool) -> Bool {
+ let locked = lock.lock(before: Date.init(timeIntervalSinceNow: waitTime))
+ if locked {
+ lock.unlock()
+ }
+ return locked == shouldLock
+ }
+
+ let thread = Thread() {
+ condition.lock()
+ condition.signal()
+ condition.unlock()
+
+ lock.lock()
+ Thread.sleep(forTimeInterval: 8)
+ lock.unlock()
+ }
+ condition.lock()
+ thread.start()
+ condition.wait()
+ condition.unlock()
+
+ XCTAssertTrue(test(waitTime: 0, shouldLock: false))
+ XCTAssertTrue(test(waitTime: -1, shouldLock: false))
+ XCTAssertTrue(test(waitTime: 1, shouldLock: false))
+ XCTAssertTrue(test(waitTime: 4, shouldLock: false))
+ XCTAssertTrue(test(waitTime: 8, shouldLock: true))
+ XCTAssertTrue(test(waitTime: -1, shouldLock: true))
+ }
+
+
+ func test_threadsAndLocks() {
+ let condition = NSCondition()
+ let lock = NSLock()
+ let threadCount = 1000
+ let endSeconds: Double = 10
+
+ let endTime = Date.init(timeIntervalSinceNow: endSeconds)
+ var threadsStarted = Array<Bool>(repeating: false, count: threadCount)
+ let arrayLock = NSLock()
+
+ for t in 0..<threadCount {
+ let thread = Thread() {
+ arrayLock.lock()
+ threadsStarted[t] = true
+ arrayLock.unlock()
+
+ condition.lock()
+ condition.wait()
+ condition.unlock()
+ for _ in 1...100 {
+ let r = (endSeconds * drand48()) / 100
+ Thread.sleep(forTimeInterval: r)
+ if lock.lock(before: endTime) {
+ lock.unlock()
+ }
+ }
+ arrayLock.lock()
+ threadsStarted[t] = false
+ arrayLock.unlock()
+ }
+ thread.start()
+ }
+
+ var totalThreads = 0
+ repeat {
+ arrayLock.lock()
+ totalThreads = threadsStarted.filter {$0 == true }.count
+ arrayLock.unlock()
+ } while totalThreads < threadCount
+ XCTAssertEqual(totalThreads, threadCount)
+
+ condition.lock()
+ condition.broadcast()
+ condition.unlock()
+
+ Thread.sleep(until: endTime)
+ repeat {
+ arrayLock.lock()
+ totalThreads = threadsStarted.filter {$0 == false }.count
+ arrayLock.unlock()
+ } while totalThreads < threadCount
+ XCTAssertEqual(totalThreads, threadCount)
+
+ let gotLock = lock.try()
+ XCTAssertTrue(gotLock)
+ if gotLock {
+ lock.unlock()
+ }
+ }
+}
diff --git a/TestFoundation/main.swift b/TestFoundation/main.swift
index a1f6bf9..227c609 100644
--- a/TestFoundation/main.swift
+++ b/TestFoundation/main.swift
@@ -111,4 +111,5 @@
testCase(TestJSONEncoder.allTests),
testCase(TestCodable.allTests),
testCase(TestUnit.allTests),
+ testCase(TestNSLock.allTests),
])