/** Control of executable units within a shared virtual memory space Copyright (C) 1996-2000 Free Software Foundation, Inc. Original Author: Scott Christley Rewritten by: Andrew Kachites McCallum Created: 1996 Rewritten by: Richard Frith-Macdonald to add optimisations features for faster thread access. Modified by: Nicola Pero to add GNUstep extensions allowing to interact with threads created by external libraries/code (eg, a Java Virtual Machine). This file is part of the GNUstep Objective-C Library. This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public License as published by the Free Software Foundation; either version 2 of the License, or (at your option) any later version. This library is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Library General Public License for more details. You should have received a copy of the GNU Lesser General Public License along with this library; if not, write to the Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02111 USA. NSThread class reference $Date$ $Revision$ */ #import "common.h" #define EXPOSE_NSThread_IVARS 1 #ifdef HAVE_NANOSLEEP #include #endif #ifdef HAVE_SYS_TIME_H #include #endif #ifdef HAVE_SYS_RESOURCE_H #include #endif #ifdef HAVE_PTHREAD_H #include #endif #if defined(HAVE_SYS_FILE_H) # include #endif #if defined(HAVE_SYS_FCNTL_H) # include #elif defined(HAVE_FCNTL_H) # include #endif #ifdef __POSIX_SOURCE #define NBLK_OPT O_NONBLOCK #else #define NBLK_OPT FNDELAY #endif #import "Foundation/NSException.h" #import "Foundation/NSThread.h" #import "Foundation/NSLock.h" #import "Foundation/NSNotification.h" #import "Foundation/NSNotificationQueue.h" #import "Foundation/NSRunLoop.h" #import "Foundation/NSConnection.h" #import "Foundation/NSInvocation.h" #import "Foundation/NSUserDefaults.h" #import "Foundation/NSGarbageCollector.h" #import "GSPrivate.h" #import "GSRunLoopCtxt.h" #if GS_WITH_GC #include #endif #if __OBJC_GC__ #include #endif #if defined(__FreeBSD__) || defined(__OpenBSD__) # include # define IS_MAIN_PTHREAD (pthread_main_np() == 1) #else # define IS_MAIN_PTHREAD (1) #endif // Some older BSD systems used a non-standard range of thread priorities. // Use these if they exist, otherwise define standard ones. #ifndef PTHREAD_MAX_PRIORITY #define PTHREAD_MAX_PRIORITY 31 #endif #ifndef PTHREAD_MIN_PRIORITY #define PTHREAD_MIN_PRIORITY 0 #endif @interface NSAutoreleasePool (NSThread) + (void) _endThread: (NSThread*)thread; @end static Class threadClass = Nil; static NSNotificationCenter *nc = nil; /** * This class performs a dual function ... *

* As a class, it is responsible for handling incoming events from * the main runloop on a special inputFd. This consumes any bytes * written to wake the main runloop.
* During initialisation, the default runloop is set up to watch * for data arriving on inputFd. *

*

* As instances, each instance retains perform receiver and argument * values as long as they are needed, and handles locking to support * methods which want to block until an action has been performed. *

*

* The initialize method of this class is called before any new threads * run. *

*/ @interface GSPerformHolder : NSObject { id receiver; id argument; SEL selector; NSConditionLock *lock; // Not retained. NSArray *modes; BOOL invalidated; } + (GSPerformHolder*) newForReceiver: (id)r argument: (id)a selector: (SEL)s modes: (NSArray*)m lock: (NSConditionLock*)l; - (void) fire; - (void) invalidate; - (BOOL) isInvalidated; - (NSArray*) modes; @end /** * Sleep until the current date/time is the specified time interval * past the reference date/time.
* Implemented as a function taking an NSTimeInterval argument in order * to avoid objc messaging and object allocation/deallocation (NSDate) * overheads.
* Used to implement [NSThread+sleepUntilDate:] * If the date is in the past, this function simply allows other threads * (if any) to run. */ void GSSleepUntilIntervalSinceReferenceDate(NSTimeInterval when) { NSTimeInterval delay; // delay is always the number of seconds we still need to wait delay = when - GSPrivateTimeNow(); if (delay <= 0.0) { sched_yield(); return; } #ifdef HAVE_NANOSLEEP // Avoid any possibility of overflow by sleeping in chunks. while (delay > 32768) { struct timespec request; request.tv_sec = (time_t)32768; request.tv_nsec = (long)0; nanosleep(&request, 0); delay = when - GSPrivateTimeNow(); } if (delay > 0) { struct timespec request; struct timespec remainder; request.tv_sec = (time_t)delay; request.tv_nsec = (long)((delay - request.tv_sec) * 1000000000); remainder.tv_sec = 0; remainder.tv_nsec = 0; /* * With nanosleep, we can restart the sleep after a signal by using * the remainder information ... so we can be sure to sleep to the * desired limit without having to re-generate the delay needed. */ while (nanosleep(&request, &remainder) < 0 && (remainder.tv_sec > 0 || remainder.tv_nsec > 0)) { request.tv_sec = remainder.tv_sec; request.tv_nsec = remainder.tv_nsec; remainder.tv_sec = 0; remainder.tv_nsec = 0; } } #else /* * Avoid integer overflow by breaking up long sleeps. */ while (delay > 30.0*60.0) { // sleep 30 minutes #if defined(__MINGW__) Sleep (30*60*1000); #else sleep (30*60); #endif delay = when - GSPrivateTimeNow(); } /* * sleeping may return early because of signals, so we need to re-calculate * the required delay and check to see if we need to sleep again. */ while (delay > 0) { #if defined(__MINGW__) #if defined(HAVE_USLEEP) /* On windows usleep() seems to perform a busy wait ... so we only * use it for short delays ... otherwise use the less accurate Sleep() */ if (delay > 0.1) { Sleep ((NSInteger)(delay*1000)); } else { usleep ((NSInteger)(delay*1000000)); } #else Sleep ((NSInteger)(delay*1000)); #endif /* HAVE_USLEEP */ #else #if defined(HAVE_USLEEP) usleep ((NSInteger)(delay*1000000)); #else sleep ((NSInteger)delay); #endif /* HAVE_USLEEP */ #endif /* __MINGW__ */ delay = when - GSPrivateTimeNow(); } #endif /* HAVE_NANOSLEEP */ } static NSArray * commonModes(void) { static NSArray *modes = nil; if (modes == nil) { [gnustep_global_lock lock]; if (modes == nil) { Class c = NSClassFromString(@"NSApplication"); SEL s = @selector(allRunLoopModes); if (c != 0 && [c respondsToSelector: s]) { modes = RETAIN([c performSelector: s]); } else { modes = [[NSArray alloc] initWithObjects: NSDefaultRunLoopMode, NSConnectionReplyMode, nil]; } } [gnustep_global_lock unlock]; } return modes; } /* * Flag indicating whether the objc runtime ever went multi-threaded. */ static BOOL entered_multi_threaded_state = NO; static NSThread *defaultThread; static pthread_key_t thread_object_key; /** * Pthread cleanup call. * * We should normally not get here ... because threads should exit properly * and clean up, so that this function doesn't get called. However if a * thread terminates for some reason without calling the exit method, we * can at least log it. * * We can't do anything more than that since at the point * when this function is called, the thread specific data is no longer * available, so the currentThread method will always fail and the * repercussions of that would well be a crash. * * As a special case, we ignore the exit of the default thread ... that one * will usually terminate without calling the exit method as it ends the * whole process by returning from the 'main' function. */ static void exitedThread(void *thread) { if (thread != defaultThread) { fprintf(stderr, "WARNING thread %p terminated without calling +exit!\n", thread); } } /** * These functions needed because sending messages to classes is a seriously * slow process with gcc and the gnu runtime. */ inline NSThread* GSCurrentThread(void) { NSThread *thr = pthread_getspecific(thread_object_key); if (nil == thr) { GSRegisterCurrentThread(); thr = pthread_getspecific(thread_object_key); if ((nil == defaultThread) && IS_MAIN_PTHREAD) { defaultThread = [thr retain]; } } assert(nil != thr && "No main thread"); return thr; } NSMutableDictionary* GSDictionaryForThread(NSThread *t) { if (nil == t) { t = GSCurrentThread(); } return [t threadDictionary]; } /** * Fast access function for thread dictionary of current thread. */ NSMutableDictionary* GSCurrentThreadDictionary(void) { return GSDictionaryForThread(nil); } /* * Callback function so send notifications on becoming multi-threaded. */ static void gnustep_base_thread_callback(void) { /* * Protect this function with locking ... to avoid any possibility * of multiple threads registering with the system simultaneously, * and so that all NSWillBecomeMultiThreadedNotifications are sent * out before any second thread can interfere with anything. */ if (entered_multi_threaded_state == NO) { [gnustep_global_lock lock]; if (entered_multi_threaded_state == NO) { /* * For apple compatibility ... and to make things easier for * code called indirectly within a will-become-multi-threaded * notification handler, we set the flag to say we are multi * threaded BEFORE sending the notifications. */ entered_multi_threaded_state = YES; #if GS_WITH_GC && defined(HAVE_GC_ALLOW_REGISTER_THREADS) /* This function needs to be called before going multi-threaded * so that the garbage collection library knows to support * registration of new threads. */ GS_allow_register_threads(); #endif NS_DURING { [GSPerformHolder class]; // Force initialization /* * Post a notification if this is the first new thread * to be created. * Won't work properly if threads are not all created * by this class, but it's better than nothing. */ if (nc == nil) { nc = RETAIN([NSNotificationCenter defaultCenter]); } #if !defined(HAVE_INITIALIZE) if (NO == [[NSUserDefaults standardUserDefaults] boolForKey: @"GSSilenceInitializeWarning"]) { NSLog(@"WARNING your program is becoming multi-threaded, but you are using an ObjectiveC runtime library which does not have a thread-safe implementation of the +initialize method. Please see README.initialize for more information."); } #endif [nc postNotificationName: NSWillBecomeMultiThreadedNotification object: nil userInfo: nil]; } NS_HANDLER { fprintf(stderr, "ALERT ... exception while becoming multi-threaded ... system may not be\n" "properly initialised.\n"); fflush(stderr); } NS_ENDHANDLER } [gnustep_global_lock unlock]; } } @implementation NSThread static void setThreadForCurrentThread(NSThread *t) { [[NSGarbageCollector defaultCollector] disableCollectorForPointer: t]; pthread_setspecific(thread_object_key, t); gnustep_base_thread_callback(); } static void unregisterActiveThread(NSThread *thread) { if (thread->_active == YES) { /* * Set the thread to be inactive to avoid any possibility of recursion. */ thread->_active = NO; thread->_finished = YES; /* * Let observers know this thread is exiting. */ if (nc == nil) { nc = RETAIN([NSNotificationCenter defaultCenter]); } [nc postNotificationName: NSThreadWillExitNotification object: thread userInfo: nil]; [(GSRunLoopThreadInfo*)thread->_runLoopInfo invalidate]; [thread release]; [[NSGarbageCollector defaultCollector] enableCollectorForPointer: thread]; pthread_setspecific(thread_object_key, nil); } } + (NSArray*) callStackReturnAddresses { NSMutableArray *stack = GSPrivateStackAddresses(); return stack; } + (BOOL) _createThreadForCurrentPthread { NSThread *t = pthread_getspecific(thread_object_key); if (t == nil) { t = [self new]; t->_active = YES; [[NSGarbageCollector defaultCollector] disableCollectorForPointer: t]; pthread_setspecific(thread_object_key, t); GS_CONSUMED(t); return YES; } return NO; } + (NSThread*) currentThread { return GSCurrentThread(); } + (void) detachNewThreadSelector: (SEL)aSelector toTarget: (id)aTarget withObject: (id)anArgument { NSThread *thread; /* * Create the new thread. */ thread = [[NSThread alloc] initWithTarget: aTarget selector: aSelector object: anArgument]; [thread start]; RELEASE(thread); } + (void) exit { NSThread *t; t = GSCurrentThread(); if (t->_active == YES) { unregisterActiveThread (t); if (t == defaultThread || defaultThread == nil) { /* For the default thread, we exit the process. */ exit(0); } else { pthread_exit(NULL); } } } /* * Class initialization */ + (void) initialize { if (self == [NSThread class]) { if (pthread_key_create(&thread_object_key, exitedThread)) { [NSException raise: NSInternalInconsistencyException format: @"Unable to create thread key!"]; } /* * Ensure that the default thread exists. */ threadClass = self; GSCurrentThread(); } } + (BOOL) isMainThread { return (GSCurrentThread() == defaultThread ? YES : NO); } + (BOOL) isMultiThreaded { return entered_multi_threaded_state; } + (NSThread*) mainThread { return defaultThread; } /** * Set the priority of the current thread. This is a value in the * range 0.0 (lowest) to 1.0 (highest) which is mapped to the underlying * system priorities. */ + (void) setThreadPriority: (double)pri { #ifdef _POSIX_THREAD_PRIORITY_SCHEDULING int policy; struct sched_param param; // Clamp pri into the required range. if (pri > 1) { pri = 1; } if (pri < 0) { pri = 0; } // Scale pri based on the range of the host system. pri *= (PTHREAD_MAX_PRIORITY - PTHREAD_MIN_PRIORITY); pri += PTHREAD_MIN_PRIORITY; pthread_getschedparam(pthread_self(), &policy, ¶m); param.sched_priority = pri; pthread_setschedparam(pthread_self(), policy, ¶m); #endif } + (void) sleepForTimeInterval: (NSTimeInterval)ti { GSSleepUntilIntervalSinceReferenceDate(GSPrivateTimeNow() + ti); } /** * Delaying a thread ... pause until the specified date. */ + (void) sleepUntilDate: (NSDate*)date { GSSleepUntilIntervalSinceReferenceDate([date timeIntervalSinceReferenceDate]); } /** * Return the priority of the current thread. */ + (double) threadPriority { double pri = 0; #ifdef _POSIX_THREAD_PRIORITY_SCHEDULING int policy; struct sched_param param; pthread_getschedparam(pthread_self(), &policy, ¶m); pri = param.sched_priority; // Scale pri based on the range of the host system. pri -= PTHREAD_MIN_PRIORITY; pri /= (PTHREAD_MAX_PRIORITY - PTHREAD_MIN_PRIORITY); #else #warning Your pthread implementation does not support thread priorities #endif return pri; } /* * Thread instance methods. */ - (void) cancel { _cancelled = YES; } - (void) dealloc { if (_active == YES) { [NSException raise: NSInternalInconsistencyException format: @"Deallocating an active thread without [+exit]!"]; } if (_runLoopInfo != 0) { GSRunLoopThreadInfo *info = (GSRunLoopThreadInfo*)_runLoopInfo; _runLoopInfo = 0; [info release]; } DESTROY(_thread_dictionary); DESTROY(_target); DESTROY(_arg); DESTROY(_name); if (_autorelease_vars.pool_cache != 0) { [NSAutoreleasePool _endThread: self]; } if (_thread_dictionary != nil) { /* * Try again to get rid of thread dictionary. */ DESTROY(_thread_dictionary); if (_autorelease_vars.pool_cache != 0) { [NSAutoreleasePool _endThread: self]; } if (_thread_dictionary != nil) { NSLog(@"Oops - leak - thread dictionary is %@", _thread_dictionary); if (_autorelease_vars.pool_cache != 0) { [NSAutoreleasePool _endThread: self]; } } } DESTROY(_gcontext); [super dealloc]; } - (id) init { init_autorelease_thread_vars(&_autorelease_vars); return self; } - (id) initWithTarget: (id)aTarget selector: (SEL)aSelector object: (id)anArgument { /* initialize our ivars. */ _selector = aSelector; _target = RETAIN(aTarget); _arg = RETAIN(anArgument); init_autorelease_thread_vars(&_autorelease_vars); return self; } - (BOOL) isCancelled { return _cancelled; } - (BOOL) isExecuting { return _active; } - (BOOL) isFinished { return _finished; } - (BOOL) isMainThread { return (self == defaultThread ? YES : NO); } - (void) main { if (_active == NO) { [NSException raise: NSInternalInconsistencyException format: @"[%@-$@] called on inactive thread", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } [_target performSelector: _selector withObject: _arg]; } - (NSString*) name { return _name; } - (void) setName: (NSString*)aName { ASSIGN(_name, aName); } - (void) setStackSize: (NSUInteger)stackSize { _stackSize = stackSize; } - (NSUInteger) stackSize { return _stackSize; } /** * Trampoline function called to launch the thread */ static void *nsthreadLauncher(void* thread) { NSThread *t = (NSThread*)thread; setThreadForCurrentThread(t); #if __OBJC_GC__ objc_registerThreadWithCollector(); #endif #if GS_WITH_GC && defined(HAVE_GC_REGISTER_MY_THREAD) { struct GC_stack_base base; if (GC_get_stack_base(&base) == GC_SUCCESS) { int result; result = GC_register_my_thread(&base); if (result != GC_SUCCESS && result != GC_DUPLICATE) { fprintf(stderr, "Argh ... no thread support in garbage collection library\n"); } } else { fprintf(stderr, "Unable to determine stack base to register new thread for garbage collection\n"); } } #endif /* * Let observers know a new thread is starting. */ if (nc == nil) { nc = RETAIN([NSNotificationCenter defaultCenter]); } [nc postNotificationName: NSThreadDidStartNotification object: t userInfo: nil]; [t main]; [NSThread exit]; // Not reached return NULL; } - (void) start { pthread_attr_t attr; pthread_t thr; if (_active == YES) { [NSException raise: NSInternalInconsistencyException format: @"[%@-$@] called on active thread", NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } if (_cancelled == YES) { [NSException raise: NSInternalInconsistencyException format: @"[%@-$@] called on cancelled thread", 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. */ gnustep_base_thread_callback(); /* The thread must persist until it finishes executing. */ [self retain]; /* Mark the thread as active whiul it's running. */ _active = YES; errno = 0; pthread_attr_init(&attr); /* Create this thread detached, because we never use the return state from * threads. */ pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); /* Set the stack size when the thread is created. Unlike the old setrlimit * code, this actually works. */ if (_stackSize > 0) { pthread_attr_setstacksize(&attr, _stackSize); } if (pthread_create(&thr, &attr, nsthreadLauncher, self)) { DESTROY(self); [NSException raise: NSInternalInconsistencyException format: @"Unable to detach thread (last error %@)", [NSError _last]]; } } /** * Return the thread dictionary. This dictionary can be used to store * arbitrary thread specific data.
* NB. This cannot be autoreleased, since we cannot be sure that the * autorelease pool for the thread will continue to exist for the entire * life of the thread! */ - (NSMutableDictionary*) threadDictionary { if (_thread_dictionary == nil) { _thread_dictionary = [NSMutableDictionary new]; } return _thread_dictionary; } @end @implementation GSRunLoopThreadInfo - (void) addPerformer: (id)performer { [lock lock]; [performers addObject: performer]; #if defined(__MINGW__) if (SetEvent(event) == 0) { NSLog(@"Set event failed - %@", [NSError _last]); } #else /* The write could concievably fail if the pipe is full. * In that case we need to release the lock teporarily to allow the other * thread to consume data from the pipe. It's possible that the thread * and its runloop might stop during that ... so we need to check that * outputFd is still valid. */ while (outputFd >= 0 && write(outputFd, "0", 1) != 1) { [lock unlock]; [lock lock]; } #endif [lock unlock]; } - (void) dealloc { [self invalidate]; DESTROY(performers); DESTROY(lock); DESTROY(loop); [super dealloc]; } - (id) init { #ifdef __MINGW__ if ((event = CreateEvent(NULL, TRUE, FALSE, NULL)) == INVALID_HANDLE_VALUE) { DESTROY(self); [NSException raise: NSInternalInconsistencyException format: @"Failed to create event to handle perform in thread"]; } #else int fd[2]; if (pipe(fd) == 0) { int e; inputFd = fd[0]; outputFd = fd[1]; if ((e = fcntl(inputFd, F_GETFL, 0)) >= 0) { e |= NBLK_OPT; if (fcntl(inputFd, F_SETFL, e) < 0) { [NSException raise: NSInternalInconsistencyException format: @"Failed to set non block flag for perform in thread"]; } } else { [NSException raise: NSInternalInconsistencyException format: @"Failed to get non block flag for perform in thread"]; } if ((e = fcntl(outputFd, F_GETFL, 0)) >= 0) { e |= NBLK_OPT; if (fcntl(outputFd, F_SETFL, e) < 0) { [NSException raise: NSInternalInconsistencyException format: @"Failed to set non block flag for perform in thread"]; } } else { [NSException raise: NSInternalInconsistencyException format: @"Failed to get non block flag for perform in thread"]; } } else { DESTROY(self); [NSException raise: NSInternalInconsistencyException format: @"Failed to create pipe to handle perform in thread"]; } #endif lock = [NSLock new]; performers = [NSMutableArray new]; return self; } - (void) invalidate { [lock lock]; [performers makeObjectsPerformSelector: @selector(invalidate)]; [performers removeAllObjects]; #ifdef __MINGW__ if (event != INVALID_HANDLE_VALUE) { CloseHandle(event); event = INVALID_HANDLE_VALUE; } #else if (inputFd >= 0) { close(inputFd); inputFd = -1; } if (outputFd >= 0) { close(outputFd); outputFd = -1; } #endif [lock unlock]; } - (void) fire { NSArray *toDo; unsigned int i; unsigned int c; [lock lock]; #if defined(__MINGW__) if (event != INVALID_HANDLE_VALUE) { if (ResetEvent(event) == 0) { NSLog(@"Reset event failed - %@", [NSError _last]); } } #else if (inputFd >= 0) { char buf[BUFSIZ]; /* We don't care how much we read. If there have been multiple * performers queued then there will be multiple bytes available, * but we always handle all available performers, so we can also * read all available bytes. * The descriptor is non-blocking ... so it's safe to ask for more * bytes than are available. */ while (read(inputFd, buf, sizeof(buf)) > 0) ; } #endif c = [performers count]; if (0 == c) { /* We deal with all available performers each time we fire, so * it's likely that we will fire when we have no performers left. * In that case we can skip the copying and emptying of the array. */ [lock unlock]; return; } toDo = [NSArray arrayWithArray: performers]; [performers removeAllObjects]; [lock unlock]; for (i = 0; i < c; i++) { GSPerformHolder *h = [toDo objectAtIndex: i]; [loop performSelector: @selector(fire) target: h argument: nil order: 0 modes: [h modes]]; } } @end GSRunLoopThreadInfo * GSRunLoopInfoForThread(NSThread *aThread) { GSRunLoopThreadInfo *info; if (aThread == nil) { aThread = GSCurrentThread(); } if (aThread->_runLoopInfo == nil) { [gnustep_global_lock lock]; if (aThread->_runLoopInfo == nil) { aThread->_runLoopInfo = [GSRunLoopThreadInfo new]; } [gnustep_global_lock unlock]; } info = aThread->_runLoopInfo; return info; } @implementation GSPerformHolder + (GSPerformHolder*) newForReceiver: (id)r argument: (id)a selector: (SEL)s modes: (NSArray*)m lock: (NSConditionLock*)l { GSPerformHolder *h; h = (GSPerformHolder*)NSAllocateObject(self, 0, NSDefaultMallocZone()); h->receiver = RETAIN(r); h->argument = RETAIN(a); h->selector = s; h->modes = RETAIN(m); h->lock = l; return h; } - (void) dealloc { DESTROY(receiver); DESTROY(argument); DESTROY(modes); if (lock != nil) { [lock lock]; [lock unlockWithCondition: 1]; lock = nil; } NSDeallocateObject(self); GSNOSUPERDEALLOC; } - (void) fire { GSRunLoopThreadInfo *threadInfo; if (receiver == nil) { return; // Already fired! } threadInfo = GSRunLoopInfoForThread(GSCurrentThread()); [threadInfo->loop cancelPerformSelectorsWithTarget: self]; [receiver performSelector: selector withObject: argument]; DESTROY(receiver); DESTROY(argument); DESTROY(modes); if (lock != nil) { NSConditionLock *l = lock; [lock lock]; lock = nil; [l unlockWithCondition: 1]; } } - (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; } @end @implementation NSObject (NSThreadPerformAdditions) - (void) performSelectorOnMainThread: (SEL)aSelector withObject: (id)anObject waitUntilDone: (BOOL)aFlag modes: (NSArray*)anArray { /* It's possible that this method could be called before the NSThread * class is initialised, so we check and make sure it's initiailised * if necessary. */ if (defaultThread == nil) { [NSThread currentThread]; } [self performSelector: aSelector onThread: defaultThread withObject: anObject waitUntilDone: aFlag modes: anArray]; } - (void) performSelectorOnMainThread: (SEL)aSelector withObject: (id)anObject waitUntilDone: (BOOL)aFlag { [self performSelectorOnMainThread: aSelector withObject: anObject waitUntilDone: aFlag modes: commonModes()]; } - (void) performSelector: (SEL)aSelector onThread: (NSThread*)aThread withObject: (id)anObject waitUntilDone: (BOOL)aFlag modes: (NSArray*)anArray { GSRunLoopThreadInfo *info; NSThread *t; if ([anArray count] == 0) { return; } t = GSCurrentThread(); if (aThread == nil) { aThread = t; } 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 order: 0 modes: anArray]; } } else { GSPerformHolder *h; NSConditionLock *l = nil; if ([t isFinished] == YES) { [NSException raise: NSInternalInconsistencyException format: @"perform on finished thread"]; } if (aFlag == YES) { l = [[NSConditionLock alloc] init]; } h = [GSPerformHolder newForReceiver: self argument: anObject selector: aSelector modes: anArray lock: l]; [info addPerformer: h]; if (l != nil) { [l lockWhenCondition: 1]; [l unlock]; RELEASE(l); if ([h isInvalidated] == YES) { [NSException raise: NSInternalInconsistencyException format: @"perform on finished thread"]; RELEASE(h); } } RELEASE(h); } } - (void) performSelector: (SEL)aSelector onThread: (NSThread*)aThread withObject: (id)anObject waitUntilDone: (BOOL)aFlag { [self performSelector: aSelector onThread: aThread withObject: anObject waitUntilDone: aFlag modes: commonModes()]; } @end /** *

* This function is provided to let threads started by some other * software library register themselves to be used with the * GNUstep system. All such threads should call this function * before attempting to use any GNUstep objects. *

*

* Returns YES if the thread can be registered, * NO if it is already registered. *

*

* Sends out a NSWillBecomeMultiThreadedNotification * if the process was not already multithreaded. *

*/ BOOL GSRegisterCurrentThread (void) { return [NSThread _createThreadForCurrentPthread]; } /** *

* This function is provided to let threads started by some other * software library unregister themselves from the GNUstep threading * system. *

*

* Calling this function causes a * NSThreadWillExitNotification * to be sent out, and destroys the GNUstep NSThread object * associated with the thread (like [NSThread+exit]) but does * not exit the underlying thread. *

*/ void GSUnregisterCurrentThread (void) { unregisterActiveThread(GSCurrentThread()); }