Skip to content

Thread: Implement more functionality #1162

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 3 commits into from
Aug 23, 2017
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
6 changes: 6 additions & 0 deletions CoreFoundation/Base.subproj/CFPlatform.c
Original file line number Diff line number Diff line change
Expand Up @@ -180,6 +180,12 @@ const char *_CFProcessPath(void) {
}
return __CFProcessPath;
}

#else

Boolean _CFIsMainThread(void) {
return pthread_main_np() == 1;
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is meant to be a Darwin implementation then?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm wondering if we should make that #else more platform specific. Other than that, this looks great to me.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I wasnt sure what other platforms support it, I thought FreeBSD might have it as well. Thought it would be easier to leave it open and then a version of _CFIsMainThread can be added to platforms as they need it.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It also works for Windows since there is a #define pthread_main_np _NS_pthread_main_np wrapped inside a #if DEPLOYMENT_TARGET_WINDOWS

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ok, I guess we'll hear back from people if this breaks their compile.

#endif

CF_PRIVATE CFStringRef _CFProcessNameString(void) {
Expand Down
2 changes: 2 additions & 0 deletions CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@
#include <CoreFoundation/ForFoundationOnly.h>
#include <fts.h>
#include <pthread.h>
#include <execinfo.h>

_CF_EXPORT_SCOPE_BEGIN

Expand Down Expand Up @@ -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);

Expand Down
2 changes: 1 addition & 1 deletion Docs/Status.md
Original file line number Diff line number Diff line change
Expand Up @@ -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 | |
Expand Down
8 changes: 6 additions & 2 deletions Foundation/NSLock.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
9 changes: 2 additions & 7 deletions Foundation/Operation.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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()
Expand Down Expand Up @@ -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
Expand Down
99 changes: 68 additions & 31 deletions Foundation/Thread.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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<T: NSObject> {
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)
}
}

Expand All @@ -57,18 +51,33 @@ private func NSThreadStart(_ context: UnsafeMutableRawPointer?) -> UnsafeMutable
}

open class Thread : NSObject {

static internal var _currentThread = NSThreadSpecific<Thread>()
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
Expand All @@ -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()
Expand Down Expand Up @@ -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
Expand All @@ -145,20 +155,20 @@ 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)
pthread_attr_setscope(attr, Int32(PTHREAD_SCOPE_SYSTEM))
pthread_attr_setdetachstate(attr, Int32(PTHREAD_CREATE_DETACHED))
}
}

public convenience init(block: @escaping () -> Swift.Void) {
self.init()
_main = block
Expand All @@ -185,11 +195,11 @@ open class Thread : NSObject {
}
#endif
}

open func main() {
_main()
}

open var name: String? {
didSet {
if _thread == Thread.current._thread {
Expand Down Expand Up @@ -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<T>(_ body: (UnsafeMutablePointer<UnsafeMutableRawPointer?>, Int) -> [T]) -> [T] {
// Same as swift/stdlib/public/runtime/Errors.cpp backtrace
let maxSupportedStackDepth = 128;
let addrs = UnsafeMutablePointer<UnsafeMutableRawPointer?>.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 "<null>"
}
return String(cString: symbol)
}
free(bs)
}
return symbols
})
}
}

Expand Down
49 changes: 43 additions & 6 deletions TestFoundation/TestThread.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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),
]
}

Expand All @@ -33,25 +36,22 @@ class TestThread : XCTestCase {
XCTAssertNotNil(thread1)
XCTAssertNotNil(thread2)
XCTAssertEqual(thread1, thread2)
XCTAssertEqual(thread1, Thread.mainThread)
}

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() {
Expand Down Expand Up @@ -84,4 +84,41 @@ 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))

let condition = NSCondition()
let thread = Thread() {
condition.lock()
XCTAssertFalse(Thread.isMainThread)
XCTAssertFalse(Thread.mainThread == Thread.current)
condition.broadcast()
condition.unlock()
}
thread.start()

condition.lock()
let ok = condition.wait(until: Date(timeIntervalSinceNow: 10))
condition.unlock()
XCTAssertTrue(ok, "NSCondition wait timed out")
}

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)
}
}
2 changes: 1 addition & 1 deletion TestFoundation/main.swift
Original file line number Diff line number Diff line change
Expand Up @@ -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),
Expand Down