blob: 4ab3ee5418e46744ab619a0e7e02671427cc103c [file] [log] [blame]
//
// GTMNSThread+Blocks.m
//
// Copyright 2012 Google Inc.
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy
// of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations
// under the License.
//
#import "GTMNSThread+Blocks.h"
#import <pthread.h>
#import <dlfcn.h>
#if NS_BLOCKS_AVAILABLE
@implementation NSThread (GTMBlocksAdditions)
+ (void)gtm_runBlockOnCurrentThread:(void (^)())block {
block();
}
- (void)gtm_performBlock:(void (^)())block {
if ([[NSThread currentThread] isEqual:self]) {
block();
} else {
[self gtm_performWaitingUntilDone:NO block:block];
}
}
- (void)gtm_performWaitingUntilDone:(BOOL)waitDone block:(void (^)())block {
[NSThread performSelector:@selector(gtm_runBlockOnCurrentThread:)
onThread:self
withObject:[[block copy] autorelease]
waitUntilDone:waitDone];
}
+ (void)gtm_performBlockInBackground:(void (^)())block {
[NSThread performSelectorInBackground:@selector(gtm_runBlockOnCurrentThread:)
withObject:[[block copy] autorelease]];
}
@end
#endif // NS_BLOCKS_AVAILABLE
enum {
kGTMSimpleThreadInitialized = 0,
kGTMSimpleThreadStarting,
kGTMSimpleThreadRunning,
kGTMSimpleThreadCancel,
kGTMSimpleThreadFinished,
};
@implementation GTMSimpleWorkerThread
- (id)init {
if ((self = [super init])) {
runLock_ =
[[NSConditionLock alloc] initWithCondition:kGTMSimpleThreadInitialized];
}
return self;
}
- (void)dealloc {
if ([self isExecuting]) {
[self stop];
}
[runLock_ release];
[super dealloc];
}
- (void)setThreadDebuggerName:(NSString *)name {
if ([name length]) {
pthread_setname_np([name UTF8String]);
} else {
pthread_setname_np("");
}
}
- (void)main {
[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.
[loop addPort:[NSPort port] forMode:NSDefaultRunLoopMode];
// 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 {
// 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);
}
}
- (void)setName:(NSString *)name {
if ([self isExecuting]) {
[self performSelector:@selector(setThreadDebuggerName:)
onThread:self
withObject:name
waitUntilDone:YES];
}
[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