From a18f91633f5bc0d805f167c340832b4515f5d682 Mon Sep 17 00:00:00 2001 From: "gtm.daemon" Date: Tue, 18 Dec 2012 00:30:10 +0000 Subject: [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) --- Foundation/GTMNSThread+BlocksTest.m | 267 ++++++++++++++++++++++++++++-------- 1 file changed, 209 insertions(+), 58 deletions(-) (limited to 'Foundation/GTMNSThread+BlocksTest.m') diff --git a/Foundation/GTMNSThread+BlocksTest.m b/Foundation/GTMNSThread+BlocksTest.m index e900b5b..606de6d 100644 --- a/Foundation/GTMNSThread+BlocksTest.m +++ b/Foundation/GTMNSThread+BlocksTest.m @@ -16,6 +16,7 @@ // under the License. // +#import #import "GTMSenTestCase.h" #import "GTMNSThread+Blocks.h" @@ -25,90 +26,240 @@ @interface GTMNSThread_BlocksTest : GTMTestCase { @private - NSThread *workerThread_; - BOOL workerRunning_; + GTMSimpleWorkerThread *workerThread_; } - -@property (nonatomic, readwrite, getter=isWorkerRunning) BOOL workerRunning; @end @implementation GTMNSThread_BlocksTest -@synthesize workerRunning = workerRunning_; - -- (void)stopTestRunning:(GTMUnitTestingBooleanRunLoopContext *)context{ - [context setShouldStop:YES]; +- (void)setUp { + workerThread_ = [[GTMSimpleWorkerThread alloc] init]; + [workerThread_ start]; } -- (void)workerMain:(id)object { - NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; - while ([self isWorkerRunning]) { - [[NSRunLoop currentRunLoop] runMode:NSDefaultRunLoopMode - beforeDate:[NSDate distantFuture]]; - } - [pool drain]; +- (void)tearDown { + [workerThread_ stop]; + [workerThread_ release]; } -- (void)killWorkerThread:(GTMUnitTestingBooleanRunLoopContext *)context { - [self setWorkerRunning:NO]; - [context setShouldStop:YES]; +- (void)testPerformBlockOnCurrentThread { + NSThread *currentThread = [NSThread currentThread]; + + GTMUnitTestingBooleanRunLoopContext *context = + [GTMUnitTestingBooleanRunLoopContext context]; + __block NSThread *runThread = nil; + + // Straight block runs right away (no runloop spin) + runThread = nil; + [context setShouldStop:NO]; + [currentThread gtm_performBlock:^{ + runThread = [NSThread currentThread]; + [context setShouldStop:YES]; + }]; + STAssertEqualObjects(runThread, currentThread, nil); + STAssertTrue([context shouldStop], nil); + + // Block with waiting runs immediately as well. + runThread = nil; + [context setShouldStop:NO]; + [currentThread gtm_performWaitingUntilDone:YES block:^{ + runThread = [NSThread currentThread]; + [context setShouldStop:YES]; + }]; + STAssertEqualObjects(runThread, currentThread, nil); + STAssertTrue([context shouldStop], nil); + + // Block without waiting requires a runloop spin. + runThread = nil; + [context setShouldStop:NO]; + [currentThread gtm_performWaitingUntilDone:NO block:^{ + runThread = [NSThread currentThread]; + [context setShouldStop:YES]; + }]; + STAssertTrue([[NSRunLoop currentRunLoop] + gtm_runUpToSixtySecondsWithContext:context], nil); + STAssertEqualObjects(runThread, currentThread, nil); + STAssertTrue([context shouldStop], nil); } -- (void)setUp { - [self setWorkerRunning:YES]; - workerThread_ = [[NSThread alloc] initWithTarget:self - selector:@selector(workerMain:) - object:nil]; - [workerThread_ start]; +- (void)testPerformBlockInBackground { + GTMUnitTestingBooleanRunLoopContext *context = + [GTMUnitTestingBooleanRunLoopContext context]; + __block NSThread *runThread = nil; + [NSThread gtm_performBlockInBackground:^{ + runThread = [NSThread currentThread]; + [context setShouldStop:YES]; + }]; + STAssertTrue([[NSRunLoop currentRunLoop] + gtm_runUpToSixtySecondsWithContext:context], nil); + STAssertNotNil(runThread, nil); + STAssertNotEqualObjects(runThread, [NSThread currentThread], nil); } -- (void)tearDown { - GTMUnitTestingBooleanRunLoopContext *context - = [GTMUnitTestingBooleanRunLoopContext context]; - [self performSelector:@selector(killWorkerThread:) - onThread:workerThread_ - withObject:context - waitUntilDone:NO]; - NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; - STAssertTrue([runLoop gtm_runUpToSixtySecondsWithContext:context], nil); - [workerThread_ release]; +- (void)testWorkerThreadBasics { + // Unstarted worker isn't running. + GTMSimpleWorkerThread *worker = [[GTMSimpleWorkerThread alloc] init]; + STAssertFalse([worker isExecuting], nil); + STAssertFalse([worker isFinished], nil); + + // Unstarted worker can be stopped without error. + [worker stop]; + STAssertFalse([worker isExecuting], nil); + STAssertTrue([worker isFinished], nil); + + // And can be stopped again + [worker stop]; + STAssertFalse([worker isExecuting], nil); + STAssertTrue([worker isFinished], nil); + + // A thread we start can be stopped with correct state. + worker = [[GTMSimpleWorkerThread alloc] init]; + STAssertFalse([worker isExecuting], nil); + STAssertFalse([worker isFinished], nil); + [worker start]; + STAssertTrue([worker isExecuting], nil); + STAssertFalse([worker isFinished], nil); + [worker stop]; + STAssertFalse([worker isExecuting], nil); + STAssertTrue([worker isFinished], nil); + + // A cancel is also honored + worker = [[GTMSimpleWorkerThread alloc] init]; + STAssertFalse([worker isExecuting], nil); + STAssertFalse([worker isFinished], nil); + [worker start]; + STAssertTrue([worker isExecuting], nil); + STAssertFalse([worker isFinished], nil); + [worker cancel]; + // And after some time we're done. We're generous here, this needs to + // exceed the worker thread's runloop timeout. + sleep(5); + STAssertFalse([worker isExecuting], nil); + STAssertTrue([worker isFinished], nil); } -- (void)testPerformBlock { - NSThread *currentThread = [NSThread currentThread]; - GTMUnitTestingBooleanRunLoopContext *context - = [GTMUnitTestingBooleanRunLoopContext context]; +- (void)testWorkerThreadStopTiming { + // Throw a sleep and make sure that we stop as soon as we can. + NSDate *start = [NSDate date]; + NSConditionLock *threadLock = [[[NSConditionLock alloc] initWithCondition:0] + autorelease]; [workerThread_ gtm_performBlock:^{ - [self performSelector:@selector(stopTestRunning:) - onThread:currentThread - withObject:context - waitUntilDone:YES]; + [threadLock lock]; + [threadLock unlockWithCondition:1]; + sleep(10); }]; - NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; - STAssertTrue([runLoop gtm_runUpToSixtySecondsWithContext:context], nil); + [threadLock lockWhenCondition:1]; + [threadLock unlock]; + [workerThread_ stop]; + STAssertFalse([workerThread_ isExecuting], nil); + STAssertTrue([workerThread_ isFinished], nil); + STAssertEqualsWithAccuracy(-[start timeIntervalSinceNow], 10.0, 2.0, nil); } -- (void)testPerformBlockWaitUntilDone { - GTMUnitTestingBooleanRunLoopContext *context - = [GTMUnitTestingBooleanRunLoopContext context]; +- (void)testPerformBlockOnWorkerThread { + GTMUnitTestingBooleanRunLoopContext *context = + [GTMUnitTestingBooleanRunLoopContext context]; + __block NSThread *runThread = nil; + + // Runs on the other thread + runThread = nil; + [context setShouldStop:NO]; + [workerThread_ gtm_performBlock:^{ + runThread = [NSThread currentThread]; + [context setShouldStop:YES]; + }]; + STAssertTrue([[NSRunLoop currentRunLoop] + gtm_runUpToSixtySecondsWithContext:context], nil); + STAssertNotNil(runThread, nil); + STAssertEqualObjects(runThread, workerThread_, nil); + + // Other thread no wait. + runThread = nil; + [context setShouldStop:NO]; + [workerThread_ gtm_performWaitingUntilDone:NO block:^{ + runThread = [NSThread currentThread]; + [context setShouldStop:YES]; + }]; + STAssertTrue([[NSRunLoop currentRunLoop] + gtm_runUpToSixtySecondsWithContext:context], nil); + STAssertNotNil(runThread, nil); + STAssertEqualObjects(runThread, workerThread_, nil); + + // Waiting requires no runloop spin + runThread = nil; + [context setShouldStop:NO]; [workerThread_ gtm_performWaitingUntilDone:YES block:^{ + runThread = [NSThread currentThread]; [context setShouldStop:YES]; }]; STAssertTrue([context shouldStop], nil); + STAssertNotNil(runThread, nil); + STAssertEqualObjects(runThread, workerThread_, nil); } -- (void)testPerformBlockInBackground { - NSThread *currentThread = [NSThread currentThread]; - GTMUnitTestingBooleanRunLoopContext *context - = [GTMUnitTestingBooleanRunLoopContext context]; - [NSThread gtm_performBlockInBackground:^{ - [self performSelector:@selector(stopTestRunning:) - onThread:currentThread - withObject:context - waitUntilDone:YES]; +- (void)testExitingBlockIsExecuting { + NSConditionLock *threadLock = [[[NSConditionLock alloc] initWithCondition:0] + autorelease]; + [workerThread_ gtm_performWaitingUntilDone:NO block:^{ + [threadLock lock]; + [threadLock unlockWithCondition:1]; + pthread_exit(NULL); + }]; + [threadLock lockWhenCondition:1]; + [threadLock unlock]; + // Give the pthread_exit() a bit of time + sleep(5); + // Did we notice the thread died? Does [... isExecuting] clean up? + STAssertFalse([workerThread_ isExecuting], nil); + STAssertTrue([workerThread_ isFinished], nil); +} + +- (void)testExitingBlockCancel { + NSConditionLock *threadLock = [[[NSConditionLock alloc] initWithCondition:0] + autorelease]; + [workerThread_ gtm_performWaitingUntilDone:NO block:^{ + [threadLock lock]; + [threadLock unlockWithCondition:1]; + pthread_exit(NULL); + }]; + [threadLock lockWhenCondition:1]; + [threadLock unlock]; + // Give the pthread_exit() a bit of time + sleep(5); + // Cancel/stop the thread + [workerThread_ stop]; + // Did we notice the thread died? Did we clean up? + STAssertFalse([workerThread_ isExecuting], nil); + STAssertTrue([workerThread_ isFinished], nil); +} + +- (void)testStopFromThread { + NSConditionLock *threadLock = [[[NSConditionLock alloc] initWithCondition:0] + autorelease]; + [workerThread_ gtm_performWaitingUntilDone:NO block:^{ + [threadLock lock]; + [workerThread_ stop]; // Shold not block. + [threadLock unlockWithCondition:1]; + }]; + // Block should complete before the stop occurs. + [threadLock lockWhenCondition:1]; + [threadLock unlock]; + // Still need to give the thread a moment to not be executing + sleep(5); + STAssertFalse([workerThread_ isExecuting], nil); + STAssertTrue([workerThread_ isFinished], nil); +} + +- (void)testPThreadName { + NSString *testName = @"InigoMontoya"; + [workerThread_ setName:testName]; + [workerThread_ gtm_performWaitingUntilDone:NO block:^{ + STAssertEqualObjects([workerThread_ name], testName, nil); + char threadName[100]; + pthread_getname_np(pthread_self(), threadName, 100); + STAssertEqualObjects([NSString stringWithUTF8String:threadName], + testName, nil); }]; - NSRunLoop *runLoop = [NSRunLoop currentRunLoop]; - STAssertTrue([runLoop gtm_runUpToSixtySecondsWithContext:context], nil); } @end -- cgit v1.2.3