diff --git a/ChangeLog b/ChangeLog index 656b6d1d3..c45f4450b 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,10 @@ +2008-03-17 Richard Frith-Macdonald + + * Headers/Foundation/NSThread.h: + * Source/NSThread.m: Add ([+isMainThread]) and ([-isFinished]). + Also check that we are not trying to perform a selector on an invalid + thread finished. + 2008-03-17 Richard Frith-Macdonald * Source/NSRunLoop.m: diff --git a/Headers/Foundation/NSThread.h b/Headers/Foundation/NSThread.h index 7eb120196..9197c9ac4 100644 --- a/Headers/Foundation/NSThread.h +++ b/Headers/Foundation/NSThread.h @@ -38,6 +38,17 @@ extern "C" { #endif +/** + * This class encapsulates OpenStep threading. See [NSLock] and its + * subclasses for handling synchronisation between threads.
+ * Each process begins with a main thread and additional threads can + * be created using NSThread. The GNUstep implementation of OpenStep + * has been carefully designed so that the internals of the base + * library do not use threading (except for methods which explicitly + * deal with threads of course) so that you can write applications + * without threading. Non-threaded applications are more efficient + * (no locking is required) and are easier to debug during development. + */ @interface NSThread : NSObject { @private @@ -48,6 +59,7 @@ extern "C" { unsigned _stackSize; BOOL _cancelled; BOOL _active; + BOOL _finished; NSHandler *_exception_handler; // Not retained. NSMutableDictionary *_thread_dictionary; struct autorelease_thread_vars _autorelease_vars; @@ -56,11 +68,46 @@ extern "C" { void *_reserved; // For future expansion } +/** + *

+ * Returns the NSThread object corresponding to the current thread. + *

+ *

+ * NB. In GNUstep the library internals use the GSCurrentThread() + * function as a more efficient mechanism for doing this job - so + * you cannot use a category to override this method and expect + * the library internals to use your implementation. + *

+ */ + (NSThread*) currentThread; + +/** + *

Create a new thread - use this method rather than alloc-init. The new + * thread will begin executing the message given by aSelector, aTarget, and + * anArgument. This should have no return value, and must set up an + * autorelease pool if retain/release memory management is used. It should + * free this pool before it finishes execution.

+ */ + (void) detachNewThreadSelector: (SEL)aSelector toTarget: (id)aTarget withObject: (id)anArgument; + +/** + * Terminates the current thread.
+ * Normally you don't need to call this method explicitly, + * since exiting the method with which the thread was detached + * causes this method to be called automatically. + */ + (void) exit; + +/** + * Returns a flag to say whether the application is multi-threaded or not.
+ * An application is considered to be multi-threaded if any thread other + * than the main thread has been started, irrespective of whether that + * thread has since terminated.
+ * NB. This method returns YES if called within a handler processing + * NSWillBecomeMultiThreadedNotification + */ + (BOOL) isMultiThreaded; + (void) sleepUntilDate: (NSDate*)date; @@ -79,6 +126,11 @@ extern "C" { */ + (NSArray*) callStackReturnAddresses; +/** Returns a boolean indicating whether this thread is the main thread of + * the process. + */ ++ (BOOL) isMainThread; + /** Returns the main thread of the process. */ + (NSThread*) mainThread; @@ -114,6 +166,11 @@ extern "C" { */ - (BOOL) isExecuting; +/** Returns a boolean indicating whether the receiving + * thread has completed executing. + */ +- (BOOL) isFinished; + /** Returns a boolean indicating whether this thread is the main thread of * the process. */ diff --git a/Source/GSPrivate.h b/Source/GSPrivate.h index b8a30c3de..376a6d5ee 100644 --- a/Source/GSPrivate.h +++ b/Source/GSPrivate.h @@ -279,6 +279,9 @@ typedef enum { * from the runloop when the event/descriptor is triggered. */ - (void) fire; +/* Cancel all pending performers. + */ +- (void) invalidate; @end /* Return (and optionally create) GSRunLoopThreadInfo for the specified diff --git a/Source/NSThread.m b/Source/NSThread.m index 9d91fa148..8cbbdcf91 100644 --- a/Source/NSThread.m +++ b/Source/NSThread.m @@ -103,6 +103,7 @@ static NSNotificationCenter *nc = nil; SEL selector; NSConditionLock *lock; // Not retained. NSArray *modes; + BOOL invalidated; } + (GSPerformHolder*) newForReceiver: (id)r argument: (id)a @@ -110,6 +111,8 @@ static NSNotificationCenter *nc = nil; modes: (NSArray*)m lock: (NSConditionLock*)l; - (void) fire; +- (void) invalidate; +- (BOOL) isInvalidated; - (NSArray*) modes; @end @@ -406,17 +409,6 @@ gnustep_base_thread_callback(void) } -/** - * This class encapsulates OpenStep threading. See [NSLock] and its - * subclasses for handling synchronisation between threads.
- * Each process begins with a main thread and additional threads can - * be created using NSThread. The GNUstep implementation of OpenStep - * has been carefully designed so that the internals of the base - * library do not use threading (except for methods which explicitly - * deal with threads of course) so that you can write applications - * without threading. Non-threaded applications are more efficient - * (no locking is required) and are easier to debug during development. - */ @implementation NSThread + (NSArray*) callStackReturnAddresses @@ -426,17 +418,6 @@ gnustep_base_thread_callback(void) return stack; } -/** - *

- * Returns the NSThread object corresponding to the current thread. - *

- *

- * NB. In GNUstep the library internals use the GSCurrentThread() - * function as a more efficient mechanism for doing this job - so - * you cannot use a category to override this method and expect - * the library internals to use your implementation. - *

- */ + (NSThread*) currentThread { NSThread *t = nil; @@ -462,13 +443,6 @@ gnustep_base_thread_callback(void) return t; } -/** - *

Create a new thread - use this method rather than alloc-init. The new - * thread will begin executing the message given by aSelector, aTarget, and - * anArgument. This should have no return value, and must set up an - * autorelease pool if retain/release memory management is used. It should - * free this pool before it finishes execution.

- */ + (void) detachNewThreadSelector: (SEL)aSelector toTarget: (id)aTarget withObject: (id)anArgument @@ -487,13 +461,6 @@ gnustep_base_thread_callback(void) RELEASE(thread); } - -/** - * Terminates the current thread.
- * Normally you don't need to call this method explicitly, - * since exiting the method with which the thread was detached - * causes this method to be called automatically. - */ + (void) exit { NSThread *t; @@ -505,6 +472,7 @@ gnustep_base_thread_callback(void) * Set the thread to be inactive to avoid any possibility of recursion. */ t->_active = NO; + t->_finished = YES; /* * Let observers know this thread is exiting. @@ -517,6 +485,8 @@ gnustep_base_thread_callback(void) object: t userInfo: nil]; + [(GSRunLoopThreadInfo*)t->_runLoopInfo invalidate]; + /* * destroy the thread object. */ @@ -558,14 +528,11 @@ gnustep_base_thread_callback(void) } } -/** - * Returns a flag to say whether the application is multi-threaded or not.
- * An application is considered to be multi-threaded if any thread other - * than the main thread has been started, irrespective of whether that - * thread has since terminated.
- * NB. This method returns YES if called within a handler processing - * NSWillBecomeMultiThreadedNotification - */ ++ (BOOL) isMainThread +{ + return (GSCurrentThread() == defaultThread ? YES : NO); +} + + (BOOL) isMultiThreaded { return entered_multi_threaded_state; @@ -710,6 +677,7 @@ gnustep_base_thread_callback(void) _exception_handler = NULL; _cancelled = NO; _active = NO; + _finished = NO; _name = nil; init_autorelease_thread_vars(&_autorelease_vars); return self; @@ -725,6 +693,11 @@ gnustep_base_thread_callback(void) return _active; } +- (BOOL) isFinished +{ + return _finished; +} + - (BOOL) isMainThread { return (self == defaultThread ? YES : NO); @@ -833,6 +806,13 @@ pthread_detach(pthread_self()); NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } + if (_finished == YES) + { + [NSException raise: NSInternalInconsistencyException + format: @"[%@-$@] called on finished thread", + NSStringFromClass([self class]), + NSStringFromSelector(_cmd)]; + } /* Make sure the notification is posted BEFORE the new thread starts. */ @@ -898,10 +878,9 @@ pthread_detach(pthread_self()); - (void) dealloc { + [self invalidate]; DESTROY(lock); DESTROY(loop); - [performers makeObjectsPerformSelector: @selector(invalidate)]; - DESTROY(performers); #ifdef __MINGW32__ if (event != INVALID_HANDLE_VALUE) { @@ -951,6 +930,14 @@ pthread_detach(pthread_self()); return self; } +- (void) invalidate +{ + [lock lock]; + [performers makeObjectsPerformSelector: @selector(invalidate)]; + [performers removeAllObjects]; + [lock unlock]; +} + - (void) fire { NSArray *toDo; @@ -1054,11 +1041,7 @@ GSRunLoopInfoForThread(NSThread *aThread) DESTROY(receiver); DESTROY(argument); DESTROY(modes); - if (lock == nil) - { - RELEASE(self); - } - else + if (lock != nil) { NSConditionLock *l = lock; @@ -1068,6 +1051,28 @@ GSRunLoopInfoForThread(NSThread *aThread) } } +- (void) invalidate +{ + if (invalidated == NO) + { + invalidated = YES; + DESTROY(receiver); + if (lock != nil) + { + NSConditionLock *l = lock; + + [lock lock]; + lock = nil; + [l unlockWithCondition: 1]; + } + } +} + +- (BOOL) isInvalidated +{ + return invalidated; +} + - (NSArray*) modes { return modes; @@ -1120,12 +1125,18 @@ GSRunLoopInfoForThread(NSThread *aThread) info = GSRunLoopInfoForThread(aThread); if (t == aThread) { + /* Perform in current thread. + */ if (aFlag == YES || info->loop == nil) { + /* Wait until done or no run loop. + */ [self performSelector: aSelector withObject: anObject]; } else { + /* Don't wait ... schedule operation in run loop. + */ [info->loop performSelector: aSelector target: self argument: anObject @@ -1138,6 +1149,11 @@ GSRunLoopInfoForThread(NSThread *aThread) GSPerformHolder *h; NSConditionLock *l = nil; + if ([t isFinished] == YES) + { + [NSException raise: NSInternalInconsistencyException + format: @"perform on finished thread"]; + } if (aFlag == YES) { l = [[NSConditionLock alloc] init]; @@ -1154,6 +1170,12 @@ GSRunLoopInfoForThread(NSThread *aThread) [l lockWhenCondition: 1]; [l unlock]; RELEASE(l); + if ([h isInvalidated] == YES) + { + [NSException raise: NSInternalInconsistencyException + format: @"perform on finished thread"]; + RELEASE(h); + } } RELEASE(h); }