From 323598ef9e50e7e3210262ddcff723af4a08dfd4 Mon Sep 17 00:00:00 2001 From: Simon Evans Date: Sun, 6 Aug 2017 08:43:49 +0100 Subject: [PATCH 1/3] Thread: Implement more functionality - Eliminate _compiler_crash_fix() as no crashes are observed anymore. - Re-enable TestThread.swift test cases. --- CoreFoundation/Base.subproj/CFPlatform.c | 6 ++ .../Base.subproj/ForSwiftFoundationOnly.h | 2 + Foundation/Operation.swift | 9 +- Foundation/Thread.swift | 99 +++++++++++++------ TestFoundation/TestThread.swift | 44 +++++++++ TestFoundation/main.swift | 2 +- 6 files changed, 123 insertions(+), 39 deletions(-) diff --git a/CoreFoundation/Base.subproj/CFPlatform.c b/CoreFoundation/Base.subproj/CFPlatform.c index 5f675109cf..6dd5564ee2 100644 --- a/CoreFoundation/Base.subproj/CFPlatform.c +++ b/CoreFoundation/Base.subproj/CFPlatform.c @@ -180,6 +180,12 @@ const char *_CFProcessPath(void) { } return __CFProcessPath; } + +#else + +Boolean _CFIsMainThread(void) { + return pthread_main_np() == 1; +} #endif CF_PRIVATE CFStringRef _CFProcessNameString(void) { diff --git a/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h b/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h index c1e36108ea..643989ebdf 100644 --- a/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h +++ b/CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h @@ -27,6 +27,7 @@ #include #include #include +#include _CF_EXPORT_SCOPE_BEGIN @@ -294,6 +295,7 @@ CF_EXPORT char *_Nullable *_Nonnull _CFEnviron(void); CF_EXPORT void CFLog1(CFLogLevel lev, CFStringRef message); CF_EXPORT Boolean _CFIsMainThread(void); +CF_EXPORT pthread_t _CFMainPThread; CF_EXPORT CFHashCode __CFHashDouble(double d); diff --git a/Foundation/Operation.swift b/Foundation/Operation.swift index 1c5529f0eb..9c17ad1db0 100644 --- a/Foundation/Operation.swift +++ b/Foundation/Operation.swift @@ -9,13 +9,8 @@ #if DEPLOYMENT_ENABLE_LIBDISPATCH import Dispatch -#if os(Linux) || os(Android) -import CoreFoundation -private func pthread_main_np() -> Int32 { - return _CFIsMainThread() ? 1 : 0 -} -#endif #endif +import CoreFoundation open class Operation : NSObject { let lock = NSLock() @@ -570,7 +565,7 @@ open class OperationQueue: NSObject { open class var current: OperationQueue? { #if DEPLOYMENT_ENABLE_LIBDISPATCH guard let specific = DispatchQueue.getSpecific(key: OperationQueue.OperationQueueKey) else { - if pthread_main_np() == 1 { + if _CFIsMainThread() { return OperationQueue.main } else { return nil diff --git a/Foundation/Thread.swift b/Foundation/Thread.swift index 3990db013e..340ec7f8c8 100644 --- a/Foundation/Thread.swift +++ b/Foundation/Thread.swift @@ -13,29 +13,23 @@ import Darwin #elseif os(Linux) || CYGWIN import Glibc #endif - import CoreFoundation -// for some reason having this take a generic causes a crash... -private func _compiler_crash_fix(_ key: _CFThreadSpecificKey, _ value: AnyObject?) { - _CThreadSpecificSet(key, value) -} - internal class NSThreadSpecific { private var key = _CFThreadSpecificKeyCreate() - + internal func get(_ generator: () -> T) -> T { if let specific = _CFThreadSpecificGet(key) { return specific as! T } else { let value = generator() - _compiler_crash_fix(key, value) + _CThreadSpecificSet(key, value) return value } } - + internal func set(_ value: T) { - _compiler_crash_fix(key, value) + _CThreadSpecificSet(key, value) } } @@ -57,18 +51,33 @@ private func NSThreadStart(_ context: UnsafeMutableRawPointer?) -> UnsafeMutable } open class Thread : NSObject { - + static internal var _currentThread = NSThreadSpecific() open class var current: Thread { return Thread._currentThread.get() { - return Thread(thread: pthread_self()) + if Thread.isMainThread { + return mainThread + } else { + return Thread(thread: pthread_self()) + } } } - - open class var isMainThread: Bool { NSUnimplemented() } - + + open class var isMainThread: Bool { + return _CFIsMainThread() + } + // !!! NSThread's mainThread property is incorrectly exported as "main", which conflicts with its "main" method. - open class var mainThread: Thread { NSUnimplemented() } + private static let _mainThread: Thread = { + var thread = Thread(thread: _CFMainPThread) + thread._status = .executing + return thread + }() + + open class var mainThread: Thread { + return _mainThread + } + /// Alternative API for detached thread creation /// - Experiment: This is a draft API currently under consideration for official import into Foundation as a suitable alternative to creation via selector @@ -77,11 +86,11 @@ open class Thread : NSObject { let t = Thread(block: block) t.start() } - + open class func isMultiThreaded() -> Bool { return true } - + open class func sleep(until date: Date) { let start_ut = CFGetSystemUptime() let start_at = CFAbsoluteTimeGetCurrent() @@ -127,9 +136,10 @@ open class Thread : NSObject { } open class func exit() { + Thread.current._status = .finished pthread_exit(nil) } - + internal var _main: () -> Void = {} #if os(OSX) || os(iOS) || CYGWIN private var _thread: pthread_t? = nil @@ -145,12 +155,12 @@ open class Thread : NSObject { internal var _cancelled = false /// - Note: this differs from the Darwin implementation in that the keys must be Strings open var threadDictionary = [String : Any]() - + internal init(thread: pthread_t) { // Note: even on Darwin this is a non-optional pthread_t; this is only used for valid threads, which are never null pointers. _thread = thread } - + public override init() { let _ = withUnsafeMutablePointer(to: &_attr) { attr in pthread_attr_init(attr) @@ -158,7 +168,7 @@ open class Thread : NSObject { pthread_attr_setdetachstate(attr, Int32(PTHREAD_CREATE_DETACHED)) } } - + public convenience init(block: @escaping () -> Swift.Void) { self.init() _main = block @@ -185,11 +195,11 @@ open class Thread : NSObject { } #endif } - + open func main() { _main() } - + open var name: String? { didSet { if _thread == Thread.current._thread { @@ -227,25 +237,52 @@ open class Thread : NSObject { open var isFinished: Bool { return _status == .finished } - + open var isCancelled: Bool { return _cancelled } - + open var isMainThread: Bool { - NSUnimplemented() + return self === Thread.mainThread } - + open func cancel() { _cancelled = true } + + private class func backtraceAddresses(_ body: (UnsafeMutablePointer, Int) -> [T]) -> [T] { + // Same as swift/stdlib/public/runtime/Errors.cpp backtrace + let maxSupportedStackDepth = 128; + let addrs = UnsafeMutablePointer.allocate(capacity: maxSupportedStackDepth) + defer { addrs.deallocate(capacity: maxSupportedStackDepth) } + let count = backtrace(addrs, Int32(maxSupportedStackDepth)) + let addressCount = max(0, min(Int(count), maxSupportedStackDepth)) + return body(addrs, addressCount) + } + open class var callStackReturnAddresses: [NSNumber] { - NSUnimplemented() + return backtraceAddresses({ (addrs, count) in + UnsafeBufferPointer(start: addrs, count: count).map { + NSNumber(value: UInt(bitPattern: $0)) + } + }) } - + open class var callStackSymbols: [String] { - NSUnimplemented() + return backtraceAddresses({ (addrs, count) in + var symbols: [String] = [] + if let bs = backtrace_symbols(addrs, Int32(count)) { + symbols = UnsafeBufferPointer(start: bs, count: count).map { + guard let symbol = $0 else { + return "" + } + return String(cString: symbol) + } + free(bs) + } + return symbols + }) } } diff --git a/TestFoundation/TestThread.swift b/TestFoundation/TestThread.swift index 56300597af..d32f3cdd84 100644 --- a/TestFoundation/TestThread.swift +++ b/TestFoundation/TestThread.swift @@ -24,6 +24,9 @@ class TestThread : XCTestCase { ("test_currentThread", test_currentThread ), ("test_threadStart", test_threadStart), ("test_threadName", test_threadName), + ("test_mainThread", test_mainThread), + ("test_callStackSymbols", test_callStackSymbols), + ("test_callStackReurnAddresses", test_callStackReturnAddresses), ] } @@ -33,6 +36,7 @@ class TestThread : XCTestCase { XCTAssertNotNil(thread1) XCTAssertNotNil(thread2) XCTAssertEqual(thread1, thread2) + XCTAssertEqual(thread1, Thread.mainThread) } func test_threadStart() { @@ -84,4 +88,44 @@ class TestThread : XCTestCase { XCTAssertEqual(thread3.name, "Thread3") XCTAssertNotEqual(thread3.name, getPThreadName()) } + + func test_mainThread() { + XCTAssertTrue(Thread.isMainThread) + let t = Thread.mainThread + XCTAssertTrue(t.isMainThread) + let c = Thread.current + XCTAssertTrue(c.isMainThread) + XCTAssertTrue(c.isExecuting) + XCTAssertTrue(c.isEqual(t)) + + var started = false + let condition = NSCondition() + let thread = Thread() { + condition.lock() + started = true + XCTAssertFalse(Thread.isMainThread) + XCTAssertFalse(Thread.mainThread == Thread.current) + condition.broadcast() + condition.unlock() + } + thread.start() + + condition.lock() + if !started { + condition.wait() + } + condition.unlock() + } + + func test_callStackSymbols() { + let symbols = Thread.callStackSymbols + XCTAssertTrue(symbols.count > 0) + XCTAssertTrue(symbols.count <= 128) + } + + func test_callStackReturnAddresses() { + let addresses = Thread.callStackReturnAddresses + XCTAssertTrue(addresses.count > 0) + XCTAssertTrue(addresses.count <= 128) + } } diff --git a/TestFoundation/main.swift b/TestFoundation/main.swift index 6e288accbc..ee02027a1d 100644 --- a/TestFoundation/main.swift +++ b/TestFoundation/main.swift @@ -73,7 +73,7 @@ XCTMain([ testCase(TestNSSet.allTests), testCase(TestStream.allTests), testCase(TestNSString.allTests), -// testCase(TestThread.allTests), + testCase(TestThread.allTests), testCase(TestProcess.allTests), testCase(TestNSTextCheckingResult.allTests), testCase(TestTimer.allTests), From fa4e41560d902c61e8d07af3a2de85af00d9cdea Mon Sep 17 00:00:00 2001 From: Simon Evans Date: Thu, 17 Aug 2017 20:49:26 +0100 Subject: [PATCH 2/3] Update implementation status for Thread to Complete --- Docs/Status.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Docs/Status.md b/Docs/Status.md index 13c9cfd2ab..fa06c03609 100644 --- a/Docs/Status.md +++ b/Docs/Status.md @@ -278,7 +278,7 @@ There is no _Complete_ status for test coverage because there are always additio | `Process` | Mostly Complete | Substantial | `interrupt()`, `terminate()`, `suspend()`, and `resume()` remain unimplemented | | `Bundle` | Mostly Complete | Incomplete | `allBundles`, `init(for:)`, `unload()`, `classNamed()`, and `principalClass` remain unimplemented | | `ProcessInfo` | Complete | Substantial | | - | `Thread` | Incomplete | Incomplete | `isMainThread`, `mainThread`, `name`, `callStackReturnAddresses`, and `callStackSymbols` remain unimplemented | + | `Thread` | Complete | Incomplete | | | `Operation` | Complete | Incomplete | | | `BlockOperation` | Complete | Incomplete | | | `OperationQueue` | Complete | Incomplete | | From dbce3c7aece487f7333bba41ddaf1d6930946fb4 Mon Sep 17 00:00:00 2001 From: Simon Evans Date: Sat, 19 Aug 2017 15:49:07 +0100 Subject: [PATCH 3/3] Thread: Improve tests - Fix NSCondition.wait(until:) to correctly calculate the timespec to use for the timeout. - Fix test_threadStart() and test_mainThread() to eliminate the `started' flag which could have a race condition and instead use NSCondition.wait(until:) to timeout the test thread, in case it does not start up or call the broadcast() function. --- Foundation/NSLock.swift | 8 ++++++-- TestFoundation/TestThread.swift | 15 ++++----------- 2 files changed, 10 insertions(+), 13 deletions(-) diff --git a/Foundation/NSLock.swift b/Foundation/NSLock.swift index e166b802f2..f4b41d0a45 100644 --- a/Foundation/NSLock.swift +++ b/Foundation/NSLock.swift @@ -230,12 +230,16 @@ open class NSCondition: NSObject, NSLocking { } var ts = timespec() ts.tv_sec = Int(floor(ti)) - ts.tv_nsec = Int((ti - Double(ts.tv_sec)) * 1000000000.0) + 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 * 1000000) / 1000000000) + 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) diff --git a/TestFoundation/TestThread.swift b/TestFoundation/TestThread.swift index d32f3cdd84..28478a4e0b 100644 --- a/TestFoundation/TestThread.swift +++ b/TestFoundation/TestThread.swift @@ -40,22 +40,18 @@ class TestThread : XCTestCase { } func test_threadStart() { - var started = false let condition = NSCondition() let thread = Thread() { condition.lock() - started = true condition.broadcast() condition.unlock() } thread.start() condition.lock() - if !started { - condition.wait() - } + let ok = condition.wait(until: Date(timeIntervalSinceNow: 10)) condition.unlock() - XCTAssertTrue(started) + XCTAssertTrue(ok, "NSCondition wait timed out") } func test_threadName() { @@ -98,11 +94,9 @@ class TestThread : XCTestCase { XCTAssertTrue(c.isExecuting) XCTAssertTrue(c.isEqual(t)) - var started = false let condition = NSCondition() let thread = Thread() { condition.lock() - started = true XCTAssertFalse(Thread.isMainThread) XCTAssertFalse(Thread.mainThread == Thread.current) condition.broadcast() @@ -111,10 +105,9 @@ class TestThread : XCTestCase { thread.start() condition.lock() - if !started { - condition.wait() - } + let ok = condition.wait(until: Date(timeIntervalSinceNow: 10)) condition.unlock() + XCTAssertTrue(ok, "NSCondition wait timed out") } func test_callStackSymbols() {