aboutsummaryrefslogtreecommitdiff
path: root/Foundation/GTMNSThread+Blocks.m
diff options
context:
space:
mode:
authorGravatar gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2012-12-18 00:30:10 +0000
committerGravatar gtm.daemon <gtm.daemon@7dc7ac4e-7543-0410-b95c-c1676fc8e2a3>2012-12-18 00:30:10 +0000
commita18f91633f5bc0d805f167c340832b4515f5d682 (patch)
tree92cae0424db673064daad791a2ac92c5c55af006 /Foundation/GTMNSThread+Blocks.m
parente352172c303e12a7807d779042a74d314f76e277 (diff)
[Author: aharper]
Improve worker thread implementation: - Handle more of the NSThread interface. - Allow cancel or stop before we've started. - Cache looked up pthread_setname_np symbol in thread local storage. R=dmaclach,thomasvl APPROVED=dmaclach DELTA=441 (339 added, 38 deleted, 64 changed)
Diffstat (limited to 'Foundation/GTMNSThread+Blocks.m')
-rw-r--r--Foundation/GTMNSThread+Blocks.m211
1 files changed, 180 insertions, 31 deletions
diff --git a/Foundation/GTMNSThread+Blocks.m b/Foundation/GTMNSThread+Blocks.m
index f835a0b..7b38f61 100644
--- a/Foundation/GTMNSThread+Blocks.m
+++ b/Foundation/GTMNSThread+Blocks.m
@@ -22,7 +22,8 @@
#import <dlfcn.h>
// Only available 10.6 and later.
-typedef int (*pthread_setname_np_Ptr)(const char*);
+typedef int (*PThreadSetNameNPPTr)(const char*);
+static PThreadSetNameNPPTr gPThreadSetNameNP = NULL;
#if NS_BLOCKS_AVAILABLE
@@ -59,63 +60,163 @@ typedef int (*pthread_setname_np_Ptr)(const char*);
#if GTM_IPHONE_SDK || (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)
enum {
- GTMSimpleThreadIsStarting = 1,
- GTMSimpleThreadIsFinished
+ kGTMSimpleThreadInitialized = 0,
+ kGTMSimpleThreadStarting,
+ kGTMSimpleThreadRunning,
+ kGTMSimpleThreadCancel,
+ kGTMSimpleThreadFinished,
};
@implementation GTMSimpleWorkerThread
++ (void)initialize {
+ if ([self class] == [GTMSimpleWorkerThread class]) {
+ // Resolve pthread_setname_np() on 10.6 and later.
+ gPThreadSetNameNP = dlsym(RTLD_DEFAULT, "pthread_setname_np");
+ }
+}
+
- (id)init {
if ((self = [super init])) {
runLock_ =
- [[NSConditionLock alloc] initWithCondition:GTMSimpleThreadIsStarting];
+ [[NSConditionLock alloc] initWithCondition:kGTMSimpleThreadInitialized];
}
return self;
}
- (void)dealloc {
+ if ([self isExecuting]) {
+ [self stop];
+ }
[runLock_ release];
[super dealloc];
}
- (void)setThreadDebuggerName:(NSString *)name {
- // [NSThread setName:] doesn't actually set the name in such a way that the
- // debugger can see it. So we handle it here instead.
- // pthread_setname_np only available 10.6 and later, look up dynamically.
- pthread_setname_np_Ptr setName = dlsym(RTLD_DEFAULT, "pthread_setname_np");
- if (!setName) return;
- setName([name UTF8String]);
+ if (gPThreadSetNameNP) gPThreadSetNameNP([name UTF8String]);
}
- (void)main {
- _GTMDevAssert([runLock_ condition] == GTMSimpleThreadIsStarting,
- @"Bad start condition %d", [runLock_ condition]);
- [runLock_ lockWhenCondition:GTMSimpleThreadIsStarting];
-
- [self setThreadDebuggerName:[self name]];
+ [runLock_ lock];
+ if ([runLock_ condition] != kGTMSimpleThreadStarting) {
+ // Don't start, we're already cancelled or we've been started twice.
+ [runLock_ unlock];
+ return;
+ }
+
+ // Give ourself an autopool
+ NSAutoreleasePool *localPool = [[NSAutoreleasePool alloc] init];
+
+ // Expose the current runloop so other threads can stop (but see caveat
+ // below).
+ NSRunLoop *loop = [NSRunLoop currentRunLoop];
+ runLoop_ = [loop getCFRunLoop];
+ if (runLoop_) CFRetain(runLoop_); // NULL check is pure paranoia.
// Add a port to the runloop so that it stays alive. Without a port attached
// to it, a runloop will immediately return when you call run on it.
- NSPort *tempPort = [NSPort port];
- NSRunLoop *loop = [NSRunLoop currentRunLoop];
- [loop addPort:tempPort forMode:NSDefaultRunLoopMode];
+ [loop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
- // Run the loop using CFRunLoopRun because [NSRunLoop run] will sometimes nest
- // runloops making it impossible to stop.
- runLoop_ = [loop getCFRunLoop];
- CFRunLoopRun();
- [runLock_ unlockWithCondition:GTMSimpleThreadIsFinished];
+ // Name ourself
+ [self setThreadDebuggerName:[self name]];
+
+ // We're officially running.
+ [runLock_ unlockWithCondition:kGTMSimpleThreadRunning];
+
+ while (![self isCancelled] &&
+ [runLock_ tryLockWhenCondition:kGTMSimpleThreadRunning]) {
+ [runLock_ unlock];
+ // We can't control nesting of runloops, so we spin with a short timeout. If
+ // another thread cancels us the CFRunloopStop() we might get it right away,
+ // if there is no nesting, otherwise our timeout will still get us to exit
+ // in reasonable time.
+ [loop runMode:NSDefaultRunLoopMode
+ beforeDate:[NSDate dateWithTimeIntervalSinceNow:1.0]];
+ [localPool drain];
+ localPool = [[NSAutoreleasePool alloc] init];
+ }
+
+ // Exit
+ [runLock_ lock];
+ [localPool drain];
+ if (runLoop_) CFRelease(runLoop_);
+ runLoop_ = NULL;
+ [runLock_ unlockWithCondition:kGTMSimpleThreadFinished];
+}
+
+- (void)start {
+ // Before we start the thread we need to make sure its not already running
+ // and that the lock is past kGTMSimpleThreadInitialized so an immediate
+ // stop is safe.
+ [runLock_ lock];
+ if ([runLock_ condition] != kGTMSimpleThreadInitialized) {
+ [runLock_ unlock];
+ return;
+ }
+ [runLock_ unlockWithCondition:kGTMSimpleThreadStarting];
+ [super start];
+}
+
+- (void)cancel {
+ // NSThread appears to not propagate [... isCancelled] to our thread in
+ // this subclass, so we'll let super know and then use our condition lock.
+ [super cancel];
+ [runLock_ lock];
+ switch ([runLock_ condition]) {
+ case kGTMSimpleThreadInitialized:
+ case kGTMSimpleThreadStarting:
+ // Cancelled before we started? Transition straight to finished.
+ [runLock_ unlockWithCondition:kGTMSimpleThreadFinished];
+ return;
+ case kGTMSimpleThreadRunning:
+ // If the thread has exited without changing lock state we detect that
+ // here. Note this is a direct call to [super isExecuting] to prevent
+ // deadlock on |runLock_| in [self isExecuting].
+ if (![super isExecuting]) {
+ // Thread died in some unanticipated way, clean up on its behalf.
+ if (runLoop_) {
+ CFRelease(runLoop_);
+ runLoop_ = NULL;
+ }
+ [runLock_ unlockWithCondition:kGTMSimpleThreadFinished];
+ return;
+ } else {
+ // We need to cancel the running loop. We'd like to stop the runloop
+ // right now if we can (but see the caveat above about nested runloops).
+ if (runLoop_) CFRunLoopStop(runLoop_);
+ [runLock_ unlockWithCondition:kGTMSimpleThreadCancel];
+ return;
+ }
+ case kGTMSimpleThreadCancel:
+ case kGTMSimpleThreadFinished:
+ // Already cancelled or finished. There's an outside chance the thread
+ // will have died now (imagine a [... dealloc] that calls pthread_exit())
+ // but we'll ignore those cases.
+ [runLock_ unlock];
+ return;
+ }
}
- (void)stop {
- CFRunLoopStop(runLoop_);
- if (![[NSThread currentThread] isEqual:self]) {
- // If we are calling stop from a separate thread, we block until the
- // simple thread actually leaves the runloop so there is no race condition
- // between the current thread and the simple thread. In effect it's a
- // join operation.
- [runLock_ lockWhenCondition:GTMSimpleThreadIsFinished];
- [runLock_ unlockWithCondition:GTMSimpleThreadIsFinished];
+ // Cancel does the heavy lifting...
+ [self cancel];
+
+ // If we're the current thread then the stop was called from within our
+ // own runloop and we need to return control now. [... main] will handle
+ // the shutdown on its own.
+ if ([[NSThread currentThread] isEqual:self]) return;
+
+ // From all other threads block till we're finished. Note that [... cancel]
+ // handles ensuring we will either already be in this state or transition
+ // there after thread exit.
+ [runLock_ lockWhenCondition:kGTMSimpleThreadFinished];
+ [runLock_ unlock];
+
+ // We could still be waiting for thread teardown at this point (lock is in
+ // the right state, but thread is not quite torn down), so spin till we say
+ // execution is complete (our implementation checks superclass).
+ while ([self isExecuting]) {
+ usleep(10);
}
}
@@ -129,6 +230,54 @@ enum {
[super setName:name];
}
+- (BOOL)isCancelled {
+ if ([super isCancelled]) return YES;
+ BOOL cancelled = NO;
+ [runLock_ lock];
+ if ([runLock_ condition] == kGTMSimpleThreadCancel) {
+ cancelled = YES;
+ }
+ [runLock_ unlock];
+ return cancelled;
+}
+
+- (BOOL)isExecuting {
+ if ([super isExecuting]) return YES;
+ [runLock_ lock];
+ switch ([runLock_ condition]) {
+ case kGTMSimpleThreadStarting:
+ // While starting we may not be executing yet, but we'll pretend we are.
+ [runLock_ unlock];
+ return YES;
+ case kGTMSimpleThreadCancel:
+ case kGTMSimpleThreadRunning:
+ // Both of these imply we're running, but [super isExecuting] failed,
+ // so the thread died for other reasons. Clean up.
+ if (runLoop_) {
+ CFRelease(runLoop_);
+ runLoop_ = NULL;
+ }
+ [runLock_ unlockWithCondition:kGTMSimpleThreadFinished];
+ break;
+ default:
+ [runLock_ unlock];
+ break;
+ }
+ return NO;
+}
+
+- (BOOL)isFinished {
+ if ([super isFinished]) return YES;
+ if ([self isExecuting]) return NO; // Will clean up dead thread.
+ BOOL finished = NO;
+ [runLock_ lock];
+ if ([runLock_ condition] == kGTMSimpleThreadFinished) {
+ finished = YES;
+ }
+ [runLock_ unlock];
+ return finished;
+}
+
@end
#endif // GTM_IPHONE_SDK || (MAC_OS_X_VERSION_MIN_REQUIRED >= MAC_OS_X_VERSION_10_5)