Skip to content

Commit 84ee193

Browse files
spevansparkera
authored andcommitted
Thread: Implement more functionality (swiftlang#1162)
* Thread: Implement more functionality - Eliminate _compiler_crash_fix() as no crashes are observed anymore. - Re-enable TestThread.swift test cases. * Update implementation status for Thread to Complete * 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.
1 parent 3f0ff62 commit 84ee193

File tree

8 files changed

+129
-48
lines changed

8 files changed

+129
-48
lines changed

CoreFoundation/Base.subproj/CFPlatform.c

+6
Original file line numberDiff line numberDiff line change
@@ -180,6 +180,12 @@ const char *_CFProcessPath(void) {
180180
}
181181
return __CFProcessPath;
182182
}
183+
184+
#else
185+
186+
Boolean _CFIsMainThread(void) {
187+
return pthread_main_np() == 1;
188+
}
183189
#endif
184190

185191
CF_PRIVATE CFStringRef _CFProcessNameString(void) {

CoreFoundation/Base.subproj/ForSwiftFoundationOnly.h

+2
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
#include <CoreFoundation/ForFoundationOnly.h>
2828
#include <fts.h>
2929
#include <pthread.h>
30+
#include <execinfo.h>
3031

3132
_CF_EXPORT_SCOPE_BEGIN
3233

@@ -294,6 +295,7 @@ CF_EXPORT char *_Nullable *_Nonnull _CFEnviron(void);
294295
CF_EXPORT void CFLog1(CFLogLevel lev, CFStringRef message);
295296

296297
CF_EXPORT Boolean _CFIsMainThread(void);
298+
CF_EXPORT pthread_t _CFMainPThread;
297299

298300
CF_EXPORT CFHashCode __CFHashDouble(double d);
299301

Docs/Status.md

+1-1
Original file line numberDiff line numberDiff line change
@@ -278,7 +278,7 @@ There is no _Complete_ status for test coverage because there are always additio
278278
| `Process` | Mostly Complete | Substantial | `interrupt()`, `terminate()`, `suspend()`, and `resume()` remain unimplemented |
279279
| `Bundle` | Mostly Complete | Incomplete | `allBundles`, `init(for:)`, `unload()`, `classNamed()`, and `principalClass` remain unimplemented |
280280
| `ProcessInfo` | Complete | Substantial | |
281-
| `Thread` | Incomplete | Incomplete | `isMainThread`, `mainThread`, `name`, `callStackReturnAddresses`, and `callStackSymbols` remain unimplemented |
281+
| `Thread` | Complete | Incomplete | |
282282
| `Operation` | Complete | Incomplete | |
283283
| `BlockOperation` | Complete | Incomplete | |
284284
| `OperationQueue` | Complete | Incomplete | |

Foundation/NSLock.swift

+6-2
Original file line numberDiff line numberDiff line change
@@ -230,12 +230,16 @@ open class NSCondition: NSObject, NSLocking {
230230
}
231231
var ts = timespec()
232232
ts.tv_sec = Int(floor(ti))
233-
ts.tv_nsec = Int((ti - Double(ts.tv_sec)) * 1000000000.0)
233+
ts.tv_nsec = Int((ti - Double(ts.tv_sec)) * 1_000_000_000.0)
234234
var tv = timeval()
235235
withUnsafeMutablePointer(to: &tv) { t in
236236
gettimeofday(t, nil)
237237
ts.tv_sec += t.pointee.tv_sec
238-
ts.tv_nsec += Int((t.pointee.tv_usec * 1000000) / 1000000000)
238+
ts.tv_nsec += Int(t.pointee.tv_usec) * 1000
239+
if ts.tv_nsec >= 1_000_000_000 {
240+
ts.tv_sec += ts.tv_nsec / 1_000_000_000
241+
ts.tv_nsec = ts.tv_nsec % 1_000_000_000
242+
}
239243
}
240244
let retVal: Int32 = withUnsafePointer(to: &ts) { t in
241245
return pthread_cond_timedwait(cond, mutex, t)

Foundation/Operation.swift

+2-7
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,8 @@
99

1010
#if DEPLOYMENT_ENABLE_LIBDISPATCH
1111
import Dispatch
12-
#if os(Linux) || os(Android)
13-
import CoreFoundation
14-
private func pthread_main_np() -> Int32 {
15-
return _CFIsMainThread() ? 1 : 0
16-
}
17-
#endif
1812
#endif
13+
import CoreFoundation
1914

2015
open class Operation : NSObject {
2116
let lock = NSLock()
@@ -570,7 +565,7 @@ open class OperationQueue: NSObject {
570565
open class var current: OperationQueue? {
571566
#if DEPLOYMENT_ENABLE_LIBDISPATCH
572567
guard let specific = DispatchQueue.getSpecific(key: OperationQueue.OperationQueueKey) else {
573-
if pthread_main_np() == 1 {
568+
if _CFIsMainThread() {
574569
return OperationQueue.main
575570
} else {
576571
return nil

Foundation/Thread.swift

+68-31
Original file line numberDiff line numberDiff line change
@@ -13,29 +13,23 @@ import Darwin
1313
#elseif os(Linux) || CYGWIN
1414
import Glibc
1515
#endif
16-
1716
import CoreFoundation
1817

19-
// for some reason having this take a generic causes a crash...
20-
private func _compiler_crash_fix(_ key: _CFThreadSpecificKey, _ value: AnyObject?) {
21-
_CThreadSpecificSet(key, value)
22-
}
23-
2418
internal class NSThreadSpecific<T: NSObject> {
2519
private var key = _CFThreadSpecificKeyCreate()
26-
20+
2721
internal func get(_ generator: () -> T) -> T {
2822
if let specific = _CFThreadSpecificGet(key) {
2923
return specific as! T
3024
} else {
3125
let value = generator()
32-
_compiler_crash_fix(key, value)
26+
_CThreadSpecificSet(key, value)
3327
return value
3428
}
3529
}
36-
30+
3731
internal func set(_ value: T) {
38-
_compiler_crash_fix(key, value)
32+
_CThreadSpecificSet(key, value)
3933
}
4034
}
4135

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

5953
open class Thread : NSObject {
60-
54+
6155
static internal var _currentThread = NSThreadSpecific<Thread>()
6256
open class var current: Thread {
6357
return Thread._currentThread.get() {
64-
return Thread(thread: pthread_self())
58+
if Thread.isMainThread {
59+
return mainThread
60+
} else {
61+
return Thread(thread: pthread_self())
62+
}
6563
}
6664
}
67-
68-
open class var isMainThread: Bool { NSUnimplemented() }
69-
65+
66+
open class var isMainThread: Bool {
67+
return _CFIsMainThread()
68+
}
69+
7070
// !!! NSThread's mainThread property is incorrectly exported as "main", which conflicts with its "main" method.
71-
open class var mainThread: Thread { NSUnimplemented() }
71+
private static let _mainThread: Thread = {
72+
var thread = Thread(thread: _CFMainPThread)
73+
thread._status = .executing
74+
return thread
75+
}()
76+
77+
open class var mainThread: Thread {
78+
return _mainThread
79+
}
80+
7281

7382
/// Alternative API for detached thread creation
7483
/// - 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 {
7786
let t = Thread(block: block)
7887
t.start()
7988
}
80-
89+
8190
open class func isMultiThreaded() -> Bool {
8291
return true
8392
}
84-
93+
8594
open class func sleep(until date: Date) {
8695
let start_ut = CFGetSystemUptime()
8796
let start_at = CFAbsoluteTimeGetCurrent()
@@ -127,9 +136,10 @@ open class Thread : NSObject {
127136
}
128137

129138
open class func exit() {
139+
Thread.current._status = .finished
130140
pthread_exit(nil)
131141
}
132-
142+
133143
internal var _main: () -> Void = {}
134144
#if os(OSX) || os(iOS) || CYGWIN
135145
private var _thread: pthread_t? = nil
@@ -145,20 +155,20 @@ open class Thread : NSObject {
145155
internal var _cancelled = false
146156
/// - Note: this differs from the Darwin implementation in that the keys must be Strings
147157
open var threadDictionary = [String : Any]()
148-
158+
149159
internal init(thread: pthread_t) {
150160
// Note: even on Darwin this is a non-optional pthread_t; this is only used for valid threads, which are never null pointers.
151161
_thread = thread
152162
}
153-
163+
154164
public override init() {
155165
let _ = withUnsafeMutablePointer(to: &_attr) { attr in
156166
pthread_attr_init(attr)
157167
pthread_attr_setscope(attr, Int32(PTHREAD_SCOPE_SYSTEM))
158168
pthread_attr_setdetachstate(attr, Int32(PTHREAD_CREATE_DETACHED))
159169
}
160170
}
161-
171+
162172
public convenience init(block: @escaping () -> Swift.Void) {
163173
self.init()
164174
_main = block
@@ -185,11 +195,11 @@ open class Thread : NSObject {
185195
}
186196
#endif
187197
}
188-
198+
189199
open func main() {
190200
_main()
191201
}
192-
202+
193203
open var name: String? {
194204
didSet {
195205
if _thread == Thread.current._thread {
@@ -227,25 +237,52 @@ open class Thread : NSObject {
227237
open var isFinished: Bool {
228238
return _status == .finished
229239
}
230-
240+
231241
open var isCancelled: Bool {
232242
return _cancelled
233243
}
234-
244+
235245
open var isMainThread: Bool {
236-
NSUnimplemented()
246+
return self === Thread.mainThread
237247
}
238-
248+
239249
open func cancel() {
240250
_cancelled = true
241251
}
242252

253+
254+
private class func backtraceAddresses<T>(_ body: (UnsafeMutablePointer<UnsafeMutableRawPointer?>, Int) -> [T]) -> [T] {
255+
// Same as swift/stdlib/public/runtime/Errors.cpp backtrace
256+
let maxSupportedStackDepth = 128;
257+
let addrs = UnsafeMutablePointer<UnsafeMutableRawPointer?>.allocate(capacity: maxSupportedStackDepth)
258+
defer { addrs.deallocate(capacity: maxSupportedStackDepth) }
259+
let count = backtrace(addrs, Int32(maxSupportedStackDepth))
260+
let addressCount = max(0, min(Int(count), maxSupportedStackDepth))
261+
return body(addrs, addressCount)
262+
}
263+
243264
open class var callStackReturnAddresses: [NSNumber] {
244-
NSUnimplemented()
265+
return backtraceAddresses({ (addrs, count) in
266+
UnsafeBufferPointer(start: addrs, count: count).map {
267+
NSNumber(value: UInt(bitPattern: $0))
268+
}
269+
})
245270
}
246-
271+
247272
open class var callStackSymbols: [String] {
248-
NSUnimplemented()
273+
return backtraceAddresses({ (addrs, count) in
274+
var symbols: [String] = []
275+
if let bs = backtrace_symbols(addrs, Int32(count)) {
276+
symbols = UnsafeBufferPointer(start: bs, count: count).map {
277+
guard let symbol = $0 else {
278+
return "<null>"
279+
}
280+
return String(cString: symbol)
281+
}
282+
free(bs)
283+
}
284+
return symbols
285+
})
249286
}
250287
}
251288

TestFoundation/TestThread.swift

+43-6
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,9 @@ class TestThread : XCTestCase {
2424
("test_currentThread", test_currentThread ),
2525
("test_threadStart", test_threadStart),
2626
("test_threadName", test_threadName),
27+
("test_mainThread", test_mainThread),
28+
("test_callStackSymbols", test_callStackSymbols),
29+
("test_callStackReurnAddresses", test_callStackReturnAddresses),
2730
]
2831
}
2932

@@ -33,25 +36,22 @@ class TestThread : XCTestCase {
3336
XCTAssertNotNil(thread1)
3437
XCTAssertNotNil(thread2)
3538
XCTAssertEqual(thread1, thread2)
39+
XCTAssertEqual(thread1, Thread.mainThread)
3640
}
3741

3842
func test_threadStart() {
39-
var started = false
4043
let condition = NSCondition()
4144
let thread = Thread() {
4245
condition.lock()
43-
started = true
4446
condition.broadcast()
4547
condition.unlock()
4648
}
4749
thread.start()
4850

4951
condition.lock()
50-
if !started {
51-
condition.wait()
52-
}
52+
let ok = condition.wait(until: Date(timeIntervalSinceNow: 10))
5353
condition.unlock()
54-
XCTAssertTrue(started)
54+
XCTAssertTrue(ok, "NSCondition wait timed out")
5555
}
5656

5757
func test_threadName() {
@@ -84,4 +84,41 @@ class TestThread : XCTestCase {
8484
XCTAssertEqual(thread3.name, "Thread3")
8585
XCTAssertNotEqual(thread3.name, getPThreadName())
8686
}
87+
88+
func test_mainThread() {
89+
XCTAssertTrue(Thread.isMainThread)
90+
let t = Thread.mainThread
91+
XCTAssertTrue(t.isMainThread)
92+
let c = Thread.current
93+
XCTAssertTrue(c.isMainThread)
94+
XCTAssertTrue(c.isExecuting)
95+
XCTAssertTrue(c.isEqual(t))
96+
97+
let condition = NSCondition()
98+
let thread = Thread() {
99+
condition.lock()
100+
XCTAssertFalse(Thread.isMainThread)
101+
XCTAssertFalse(Thread.mainThread == Thread.current)
102+
condition.broadcast()
103+
condition.unlock()
104+
}
105+
thread.start()
106+
107+
condition.lock()
108+
let ok = condition.wait(until: Date(timeIntervalSinceNow: 10))
109+
condition.unlock()
110+
XCTAssertTrue(ok, "NSCondition wait timed out")
111+
}
112+
113+
func test_callStackSymbols() {
114+
let symbols = Thread.callStackSymbols
115+
XCTAssertTrue(symbols.count > 0)
116+
XCTAssertTrue(symbols.count <= 128)
117+
}
118+
119+
func test_callStackReturnAddresses() {
120+
let addresses = Thread.callStackReturnAddresses
121+
XCTAssertTrue(addresses.count > 0)
122+
XCTAssertTrue(addresses.count <= 128)
123+
}
87124
}

TestFoundation/main.swift

+1-1
Original file line numberDiff line numberDiff line change
@@ -73,7 +73,7 @@ XCTMain([
7373
testCase(TestNSSet.allTests),
7474
testCase(TestStream.allTests),
7575
testCase(TestNSString.allTests),
76-
// testCase(TestThread.allTests),
76+
testCase(TestThread.allTests),
7777
testCase(TestProcess.allTests),
7878
testCase(TestNSTextCheckingResult.allTests),
7979
testCase(TestTimer.allTests),

0 commit comments

Comments
 (0)