diff --git a/ChangeLog b/ChangeLog index bf4879b71..f5084fe5c 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,32 @@ +2009-09-01 David Chisnall + + * Source/NSLock.m + * Headers/Foundation/NSLock.h + Completely rewritten implementations of NSLock.h classes. These are now + faster, more complete, OS X-compatible, and most importantly actually + work. The old ones, for example, called functions that were not + implemented on Windows. + * Source/NSThread.m + Call pthread functions directly in NSThread instead of via the libobjc + abstraction layer. Also fixed a few issues, such as GC not being + initialized properly for NSThread subclasses that override -main (Javaism + supported by OS X) and tidies up the code in several places, removing + premature optimizations, especially those that introduce a test for an + unlikely case at the start of a method and thus slow everything down. + + As a result of this change, GNUstep now depends on an implementation of + POSIX threads. This is included as standard on all modern UNIX systems, + and as an option on less-modern UNIX systems and non-UNIX systems, + including Windows. If you are building GNUstep on Windows, please install + the pthreads-win32 package, available from: + + http://sourceware.org/pthreads-win32/ + + PLEASE TEST THIS! There may be some code that depended on the old + behaviour. I have been running the new NSLock implementation on FreeBSD + for a few weeks without issue; please report to me any problems that you + have on your platform. + 2009-09-01 Richard Frith-Macdonald * Version: bump to 1.19.3 diff --git a/Headers/Foundation/NSLock.h b/Headers/Foundation/NSLock.h index de7335f01..805e7f7cf 100644 --- a/Headers/Foundation/NSLock.h +++ b/Headers/Foundation/NSLock.h @@ -35,6 +35,8 @@ #import +#include + #if defined(__cplusplus) extern "C" { #endif @@ -58,12 +60,20 @@ extern "C" { /** * Simplest lock for protecting critical sections of code. + * + * An NSLock is used in multi-threaded applications to protect + * critical pieces of code. While one thread holds a lock within a piece of + * code, another thread cannot execute that code until the first thread has + * given up its hold on the lock. The limitation of NSLock is + * that you can only lock an NSLock once and it must be unlocked + * before it can be acquired again.
Other lock classes, notably + * [NSRecursiveLock], have different restrictions. */ @interface NSLock : NSObject { @private - void *_mutex; - NSString *_name; + pthread_mutex_t _mutex; + NSString *_name; } /** @@ -98,6 +108,46 @@ extern "C" { @end +/** + * NSCondition provides an interface to POSIX condition variables. + */ +@interface NSCondition : NSObject +{ +@private + pthread_cond_t _condition; + pthread_mutex_t _mutex; + NSString *_name; +} +/** + * Blocks atomically unlocks the receiver. This method should only be called + * when the receiver is locked. The caller will then block until the receiver + * is sent either a -signal or -broadcast message from another thread. At this + * point, the calling thread will reacquire the lock. + */ +- (void)wait; +/** + * Blocks the calling thread and acquires the lock, in the same way as -wait. + * Returns YES if the condition is signaled, or NO if the timeout is reached. + */ +- (BOOL)waitUntilDate: (NSDate*)limit; +/** + * Wakes a single thread that is waiting on this condition. + */ +- (void)signal; +/** + * Wakes all threads that are waiting on this condition. + */ +- (void)broadcast; +/** + * Sets the name used for debugging messages. + */ +- (void)setName:(NSString*)newName; +/** + * Returns the name used for debugging messages. + */ +- (NSString*)name; +@end + /** * Lock that allows user to request it only when an internal integer * condition is equal to a particular value. The condition is set on @@ -106,8 +156,7 @@ extern "C" { @interface NSConditionLock : NSObject { @private - void *_condition; - void *_mutex; + NSCondition *_condition; int _condition_value; NSString *_name; } @@ -198,7 +247,7 @@ extern "C" { @interface NSRecursiveLock : NSObject { @private - void *_mutex; + pthread_mutex_t _mutex; NSString *_name; } diff --git a/Source/NSLock.m b/Source/NSLock.m index 8de3968de..26318382c 100644 --- a/Source/NSLock.m +++ b/Source/NSLock.m @@ -1,11 +1,7 @@ -/** Mutual exclusion locking classes - Copyright (C) 1996,2003 Free Software Foundation, Inc. +/** Control of executable units within a shared virtual memory space + Copyright (C) 1996-2000 Free Software Foundation, Inc. - Author: Scott Christley - Created: 1996 - Author: Richard Frith-Macdonald - - This file is part of the GNUstep Objective-C Library. + Original Author: David Chisnall This library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public @@ -23,723 +19,303 @@ Boston, MA 02111 USA. NSLock class reference - $Date$ $Revision$ */ -#include "config.h" -#include -#ifdef HAVE_UNISTD_H -#include -#endif -#include "GNUstepBase/preface.h" + + +// This file uses some SUS'98 extensions, so we need to tell glibc not to hide +// them. Other platforms have more sensible libcs, which just default to being +// standards-compliant. +#define _XOPEN_SOURCE 500 #include "Foundation/NSLock.h" +#include +#include #include "Foundation/NSException.h" -#include "Foundation/NSDebug.h" -#include "Foundation/NSThread.h" -#ifdef NeXT_RUNTIME -#include "thr-mach.h" -#endif -#define _MUTEX ((objc_mutex_t)_mutex) -#define _CONDITION ((objc_condition_t)_condition) +/** + * Methods shared between NSLock, NSRecursiveLock, and NSCondition + * + * Note: These methods currently throw exceptions when locks are incorrectly + * acquired. This is compatible with earlier GNUstep behaviour. In OS X 10.5 + * and later, these will just NSLog a warning instead. Throwing an exception + * is probably better behaviour, because it encourages developer to fix their + * code. + */ +#define NSLOCKING_METHODS \ +- (void)lock\ +{\ + int err = pthread_mutex_lock(&_mutex);\ + if (EINVAL == err)\ + {\ + [NSException raise: NSLockException\ + format: @"failed to unlock mutex"];\ + }\ + if (EDEADLK == err)\ + {\ + _NSLockError(self, _cmd);\ + }\ +}\ +- (void)unlock\ +{\ + if (0 != pthread_mutex_unlock(&_mutex))\ + {\ + [NSException raise: NSLockException\ + format: @"failed to unlock mutex"];\ + }\ +}\ +- (NSString*) description\ +{\ + if (_name == nil)\ + {\ + return [super description];\ + }\ + return [NSString stringWithFormat: @"%@ '%@'",\ + [super description], _name];\ +}\ +- (BOOL) tryLock\ +{\ + int err = pthread_mutex_trylock(&_mutex);\ + if (EDEADLK == err)\ + {\ + _NSLockError(self, _cmd);\ + return YES;\ + }\ + return (0 == err);\ +}\ +- (BOOL) lockBeforeDate: (NSDate*)limit\ +{\ + do\ + {\ + int err = pthread_mutex_trylock(&_mutex);\ + if (EDEADLK == err)\ + {\ + _NSLockError(self, _cmd);\ + return YES;\ + }\ + if (0 == err)\ + {\ + return YES;\ + }\ + sched_yield();\ + } while([limit timeIntervalSinceNow] < 0);\ + return NO;\ +}\ +NAME_METHODS -extern void GSSleepUntilIntervalSinceReferenceDate(NSTimeInterval); -extern NSTimeInterval GSTimeNow(); - -typedef struct { - NSTimeInterval end; - NSTimeInterval i0; - NSTimeInterval i1; - NSTimeInterval max; -} GSSleepInfo; - -static void GSSleepInit(NSDate *limit, GSSleepInfo *context) -{ - context->end = [limit timeIntervalSinceReferenceDate]; - context->i0 = 0.0; - context->i1 = 0.0001; // Initial pause interval. - context->max = 0.25; // Maximum pause interval. +#define NAME_METHODS \ +- (void)setName:(NSString*)newName\ +{\ + ASSIGNCOPY(_name, newName);\ +}\ +- (NSString*)name\ +{\ + return _name;\ } /** - *

Using a pointer to a context structure initialised using GSSleepInit() - * we either pause for a while and return YES or, if the limit date - * has passed, return NO. - *

- *

The pause intervals start off very small, but rapidly increase - * (following a fibonacci sequence) up to a maximum value. - *

- *

We use the GSSleepUntilIntervalSinceReferenceDate() function to - * avoid objc runtime messaging overheads and overheads of creating and - * destroying temporary date objects. - *

+ * OS X 10.5 compatibility function to allow debugging deadlock conditions. + * + * On OS X, this really deadlocks. For now, we just continue, while logging + * the 'you are a numpty' warning. */ -static BOOL GSSleepOrFail(GSSleepInfo *context) +void _NSLockError(id obj, SEL _cmd) { - NSTimeInterval when = GSTimeNow(); - NSTimeInterval tmp; + NSLog(@"*** -[%@ %@]: deadlock (%@)", [obj class], + NSStringFromSelector(_cmd), obj); + NSLog(@"*** Break on _NSLockError() to debug."); +} - if (when >= context->end) - { - return NO; - } - tmp = context->i0 + context->i1; - context->i0 = context->i1; - context->i1 = tmp; - if (tmp > context->max) - { - tmp = context->max; - } - when += tmp; - if (when > context->end) - { - when = context->end; - } - GSSleepUntilIntervalSinceReferenceDate(when); - return YES; // Paused. +/** + * Init method for an NSLock / NSRecursive lock. Creates a mutex of the + * specified type. Also adds the corresponding -finalize and -dealloc methods. + */ +#define INIT_LOCK_WITH_TYPE(lock_type) \ +- (id) init\ +{\ + if (nil == (self = [super init])) { return nil; }\ + pthread_mutexattr_t attr;\ + pthread_mutexattr_init(&attr);\ + pthread_mutexattr_settype(&attr, lock_type);\ + if (0 != pthread_mutex_init(&_mutex, &attr))\ + {\ + [self release];\ + return nil;\ + }\ + return self;\ +}\ +- (void) finalize\ +{\ + pthread_mutex_destroy(&_mutex);\ +}\ +- (void) dealloc\ +{\ + [self finalize];\ + [_name release];\ + [super dealloc];\ } // Exceptions NSString *NSLockException = @"NSLockException"; -NSString *NSConditionLockException = @"NSConditionLockException"; -NSString *NSRecursiveLockException = @"NSRecursiveLockException"; -// Macros -#define CHECK_RECURSIVE_LOCK(mutex) \ -{ \ - if ((mutex)->owner == objc_thread_id()) \ - { \ - [NSException \ - raise: NSLockException \ - format: @"Thread attempted to recursively lock"]; \ - /* NOT REACHED */ \ - } \ -} - -#define CHECK_RECURSIVE_CONDITION_LOCK(mutex) \ -{ \ - if ((mutex)->owner == objc_thread_id()) \ - { \ - [NSException \ - raise: NSConditionLockException \ - format: @"Thread attempted to recursively lock"]; \ - /* NOT REACHED */ \ - } \ -} - -// NSLock class -// Simplest lock for protecting critical sections of code - -/** - * An NSLock is used in multi-threaded applications to protect - * critical pieces of code. While one thread holds a lock within a piece of - * code, another thread cannot execute that code until the first thread has - * given up its hold on the lock. The limitation of NSLock is - * that you can only lock an NSLock once and it must be unlocked - * before it can be acquired again.
Other lock classes, notably - * [NSRecursiveLock], have different restrictions. - */ @implementation NSLock - -// Designated initializer -- (id) init -{ - self = [super init]; - if (self != nil) - { - // Allocate the mutex from the runtime - _mutex = objc_mutex_allocate(); - if (_mutex == 0) - { - RELEASE(self); - NSLog(@"Failed to allocate a mutex"); - return nil; - } - } - return self; -} - -- (void) dealloc -{ - [self finalize]; - [super dealloc]; -} - -- (NSString*) description -{ - if (_name == nil) - return [super description]; - return [NSString stringWithFormat: @"%@ named '%@'", - [super description], _name]; -} - -- (void) finalize -{ - if (_mutex != 0) - { - objc_mutex_t tmp = _MUTEX; - - _mutex = 0; - // Ask the runtime to deallocate the mutex - // If there are outstanding locks then it will block - if (objc_mutex_deallocate(tmp) == -1) - { - NSWarnMLog(@"objc_mutex_deallocate() failed for %@", self); - } - } - DESTROY(_name); -} - -- (NSString*) name -{ - return _name; -} - -- (void) setName: (NSString*)name -{ - ASSIGNCOPY(_name, name); -} - -/** - * Attempts to acquire a lock, but returns immediately if the lock - * cannot be acquired. It returns YES if the lock is acquired. It returns - * NO if the lock cannot be acquired or if the current thread already has - * the lock. - */ -- (BOOL) tryLock -{ - /* Return NO if we're already locked */ - if (_MUTEX->owner == objc_thread_id()) - { - return NO; - } - - // Ask the runtime to acquire a lock on the mutex - if (objc_mutex_trylock(_MUTEX) == -1) - { - return NO; - } - return YES; -} - -/** - * Attempts to acquire a lock before the date limit passes. It returns YES - * if it can. It returns NO if it cannot, or if the current thread already - * has the lock (but it waits until the time limit is up before returning - * NO). - */ -- (BOOL) lockBeforeDate: (NSDate*)limit -{ - int x; - GSSleepInfo ctxt; - - GSSleepInit(limit, &ctxt); - - /* This is really the behavior of OpenStep, if the current thread has - the lock, we just block until the time limit is up. Very odd */ - while (_MUTEX->owner == objc_thread_id() - || (x = objc_mutex_trylock(_MUTEX)) == -1) - { - if (GSSleepOrFail(&ctxt) == NO) - { - return NO; - } - } - return YES; -} - -/** - * Attempts to acquire a lock, and waits until it can do so. - */ -- (void) lock -{ - CHECK_RECURSIVE_LOCK(_MUTEX); - - // Ask the runtime to acquire a lock on the mutex - // This will block - if (objc_mutex_lock(_MUTEX) == -1) - { - [NSException raise: NSLockException - format: @"failed to lock mutex"]; - /* NOT REACHED */ - } -} - -- (void) unlock -{ - // Ask the runtime to release a lock on the mutex - if (objc_mutex_unlock(_MUTEX) == -1) - { - [NSException raise: NSLockException - format: @"unlock: failed to unlock mutex"]; - /* NOT REACHED */ - } -} - +// Use an error-checking lock. This is marginally slower, but lets us throw +// exceptions when incorrect locking occurs. +INIT_LOCK_WITH_TYPE(PTHREAD_MUTEX_ERRORCHECK) +NSLOCKING_METHODS @end - -// NSConditionLock -// Allows locking and unlocking to be based upon an integer condition +@implementation NSRecursiveLock +INIT_LOCK_WITH_TYPE(PTHREAD_MUTEX_RECURSIVE); +NSLOCKING_METHODS +@end + +@implementation NSCondition +- (id)init +{ + if (nil == (self = [super init])) { return nil; } + if (0 != pthread_cond_init(&_condition, NULL)) + { + [self release]; + return nil; + } + if (0 != pthread_mutex_init(&_mutex, NULL)) + { + pthread_cond_destroy(&_condition); + [self release]; + return nil; + } + return self; +} +- (void)finalize +{ + pthread_cond_destroy(&_condition); + pthread_mutex_destroy(&_mutex); +} +- (void)dealloc +{ + [self finalize]; + [_name release]; + [super dealloc]; +} +- (void)wait +{ + pthread_cond_wait(&_condition, &_mutex); +} +- (BOOL)waitUntilDate: (NSDate*)limit +{ + NSTimeInterval t = [limit timeIntervalSinceReferenceDate]; + double secs, subsecs; + struct timespec timeout; + // Split the float into seconds and fractions of a second + subsecs = modf(t, &secs); + timeout.tv_sec = secs; + // Convert fractions of a second to nanoseconds + timeout.tv_nsec = subsecs * 1e9; + return (0 == pthread_cond_timedwait(&_condition, &_mutex, &timeout)); +} +- (void)signal +{ + pthread_cond_signal(&_condition); +} +- (void)broadcast; +{ + pthread_cond_broadcast(&_condition); +} +NSLOCKING_METHODS +@end @implementation NSConditionLock - - (id) init { return [self initWithCondition: 0]; } -// Designated initializer -// Initialize lock with condition - (id) initWithCondition: (NSInteger)value { - self = [super init]; - if (self != nil) - { - _condition_value = value; - - // Allocate the mutex from the runtime - _condition = objc_condition_allocate (); - if (_condition == 0) + if (nil == (self = [super init])) { return nil; } + if (nil == (_condition = [NSCondition new])) { - NSLog(@"Failed to allocate a condition"); - RELEASE(self); - return nil; + [self release]; + return nil; } - _mutex = objc_mutex_allocate (); - if (_mutex == 0) - { - NSLog(@"Failed to allocate a mutex"); - RELEASE(self); - return nil; - } - } - return self; + _condition_value = value; + return self; } - (void) dealloc { - [self finalize]; + [_name release]; + [_condition release]; [super dealloc]; } -- (NSString*) description -{ - if (_name == nil) - return [super description]; - return [NSString stringWithFormat: @"%@ named '%@'", - [super description], _name]; -} - -- (void) finalize -{ - if (_condition != 0) - { - objc_condition_t tmp = _CONDITION; - - _condition = 0; - // Ask the runtime to deallocate the condition - if (objc_condition_deallocate(tmp) == -1) - { - NSWarnMLog(@"objc_condition_deallocate() failed for %@", self); - } - } - if (_mutex != 0) - { - objc_mutex_t tmp = _MUTEX; - - _mutex = 0; - // Ask the runtime to deallocate the mutex - // If there are outstanding locks then it will block - if (objc_mutex_deallocate(tmp) == -1) - { - NSWarnMLog(@"objc_mutex_deallocate() failed for %@", self); - } - } - DESTROY(_name); -} - -// Return the current condition of the lock - (NSInteger) condition { return _condition_value; } -// Acquiring and release the lock - (void) lockWhenCondition: (NSInteger)value { - CHECK_RECURSIVE_CONDITION_LOCK(_MUTEX); - - if (objc_mutex_lock(_MUTEX) == -1) - { - [NSException raise: NSConditionLockException - format: @"lockWhenCondition: failed to lock mutex"]; - /* NOT REACHED */ - } - - while (_condition_value != value) - { - if (objc_condition_wait(_CONDITION, _MUTEX) == -1) - { - [NSException raise: NSConditionLockException - format: @"objc_condition_wait failed"]; - /* NOT REACHED */ - } - } -} - -- (NSString*) name -{ - return _name; -} - -- (void) setName: (NSString*)name -{ - ASSIGNCOPY(_name, name); + [_condition lock]; + while (value != _condition_value) + { + [_condition wait]; + } } - (void) unlockWithCondition: (NSInteger)value { - int depth; - - // First check to make sure we have the lock - depth = objc_mutex_trylock(_MUTEX); - - // Another thread has the lock so abort - if (depth == -1) - { - [NSException raise: NSConditionLockException - format: @"unlockWithCondition: Tried to unlock someone else's lock"]; - /* NOT REACHED */ - } - - // If the depth is only 1 then we just acquired - // the lock above, bogus unlock so abort - if (depth == 1) - { - [NSException raise: NSConditionLockException - format: @"unlockWithCondition: Unlock attempted without lock"]; - /* NOT REACHED */ - } - - // This is a valid unlock so set the condition - _condition_value = value; - - // wake up blocked threads - if (objc_condition_broadcast(_CONDITION) == -1) - { - [NSException raise: NSConditionLockException - format: @"unlockWithCondition: objc_condition_broadcast failed"]; - /* NOT REACHED */ - } - - // and unlock twice - if ((objc_mutex_unlock(_MUTEX) == -1) - || (objc_mutex_unlock(_MUTEX) == -1)) - { - [NSException raise: NSConditionLockException - format: @"unlockWithCondition: failed to unlock mutex"]; - /* NOT REACHED */ - } -} - -- (BOOL) tryLock -{ - if ((_MUTEX)->owner == objc_thread_id()) - { - NSDebugLog(@"WARNING: Thread attempted to recursively tryLock : %@", self); - return NO; - } - // Ask the runtime to acquire a lock on the mutex - if (objc_mutex_trylock(_MUTEX) == -1) - return NO; - else - return YES; + [_condition lock]; + _condition_value = value; + [_condition broadcast]; + [_condition unlock]; } - (BOOL) tryLockWhenCondition: (NSInteger)value { - // tryLock message will check for recursive locks - - // First can we even get the lock? - if (![self tryLock]) - return NO; - - // If we got the lock is it the right condition? - if (_condition_value == value) - return YES; - else - { - // Wrong condition so release the lock - [self unlock]; - return NO; - } + return [self lockWhenCondition: value + beforeDate: [NSDate date]]; } -// Acquiring the lock with a date condition - (BOOL) lockBeforeDate: (NSDate*)limit { - GSSleepInfo ctxt; - - CHECK_RECURSIVE_CONDITION_LOCK(_MUTEX); - - GSSleepInit(limit, &ctxt); - - while (objc_mutex_trylock(_MUTEX) == -1) - { - if (GSSleepOrFail(&ctxt) == NO) - { - return NO; - } - } - return YES; + return [_condition lockBeforeDate: limit]; } - - (BOOL) lockWhenCondition: (NSInteger)condition_to_meet beforeDate: (NSDate*)limitDate { -#ifndef HAVE_OBJC_CONDITION_TIMEDWAIT - GSSleepInfo ctxt; - - CHECK_RECURSIVE_CONDITION_LOCK(_MUTEX); - - GSSleepInit(limitDate, &ctxt); - - do - { - if (_condition_value == condition_to_meet) + [_condition lock]; + if (condition_to_meet == _condition_value) { - while (objc_mutex_trylock(_MUTEX) == -1) - { - if (GSSleepOrFail(&ctxt) == NO) - { - return NO; - } - } - if (_condition_value == condition_to_meet) - { - return YES; - } - if (objc_mutex_unlock(_MUTEX) == -1) - { - [NSException raise: NSConditionLockException - format: @"%s failed to unlock mutex", - GSNameFromSelector(_cmd)]; - /* NOT REACHED */ - } + return YES; } - } - while (GSSleepOrFail(&ctxt) == YES); - - return NO; - -#else - NSTimeInterval atimeinterval; - struct timespec endtime; - - CHECK_RECURSIVE_CONDITION_LOCK(_MUTEX); - - if (-1 == objc_mutex_lock(_MUTEX)) - [NSException raise: NSConditionLockException - format: @"lockWhenCondition: failed to lock mutex"]; - - if (_condition_value == condition_to_meet) - return YES; - - atimeinterval = [limitDate timeIntervalSince1970]; - endtime.tv_sec =(NSUInteger)atimeinterval; // 941883028;// - endtime.tv_nsec = (NSUInteger)((atimeinterval - (float)endtime.tv_sec) - * 1000000000.0); - - while (_condition_value != condition_to_meet) - { - switch (objc_condition_timedwait(_CONDITION, _MUTEX, &endtime)) + if ([_condition waitUntilDate: limitDate] + && + (condition_to_meet == _condition_value)) { - case 0: - break; - case EINTR: - break; - case ETIMEDOUT : - [self unlock]; - return NO; - default: - [NSException raise: NSConditionLockException - format: @"objc_condition_timedwait failed"]; - [self unlock]; - return NO; + return YES; } - } - return YES; -#endif /* HAVE__OBJC_CONDITION_TIMEDWAIT */ + return NO; } -// NSLocking protocol -// These methods ignore the condition +// NSLocking methods. These aren't instantiated with the macro as they are +// delegated to the NSCondition. - (void) lock { - CHECK_RECURSIVE_CONDITION_LOCK(_MUTEX); - - // Ask the runtime to acquire a lock on the mutex - // This will block - if (objc_mutex_lock(_MUTEX) == -1) - { - [NSException raise: NSConditionLockException - format: @"lock: failed to lock mutex"]; - /* NOT REACHED */ - } + [_condition lock]; } - (void) unlock { - // wake up blocked threads - if (objc_condition_broadcast(_CONDITION) == -1) - { - [NSException raise: NSConditionLockException - format: @"unlockWithCondition: objc_condition_broadcast failed"]; - /* NOT REACHED */ - } - - // Ask the runtime to release a lock on the mutex - if (objc_mutex_unlock(_MUTEX) == -1) - { - [NSException raise: NSConditionLockException - format: @"unlock: failed to unlock mutex"]; - /* NOT REACHED */ - } + [_condition unlock]; } -@end - - - -/** - * See [NSLock] for more information about what a lock is. A recursive - * lock extends [NSLock] in that you can lock a recursive lock multiple - * times. Each lock must be balanced by a corresponding unlock, and the - * lock is not released for another thread to acquire until the last - * unlock call is made (corresponding to the first lock message). - */ -@implementation NSRecursiveLock - -/** - */ -- (id) init -{ - self = [super init]; - if (self != nil) - { - // Allocate the mutex from the runtime - _mutex = objc_mutex_allocate(); - if (_mutex == 0) - { - NSLog(@"Failed to allocate a mutex"); - RELEASE(self); - return nil; - } - } - return self; -} - -- (void) dealloc -{ - [self finalize]; - [super dealloc]; -} - -- (NSString*) description -{ - if (_name == nil) - return [super description]; - return [NSString stringWithFormat: @"%@ named '%@'", - [super description], _name]; -} - -- (void) finalize -{ - if (_mutex != 0) - { - objc_mutex_t tmp = _MUTEX; - - _mutex = 0; - // Ask the runtime to deallocate the mutex - // If there are outstanding locks then it will block - if (objc_mutex_deallocate(tmp) == -1) - { - NSWarnMLog(@"objc_mutex_deallocate() failed for %@", self); - } - } - DESTROY(_name); -} - -- (NSString*) name -{ - return _name; -} - -- (void) setName: (NSString*)name -{ - ASSIGNCOPY(_name, name); -} - -/** - * Attempts to acquire a lock, but returns NO immediately if the lock - * cannot be acquired. It returns YES if the lock is acquired. Can be - * called multiple times to make nested locks. - */ - (BOOL) tryLock { - // Ask the runtime to acquire a lock on the mutex - if (objc_mutex_trylock(_MUTEX) == -1) - return NO; - else - return YES; + return [_condition tryLock]; } - -/** - * Attempts to acquire a lock before the date limit passes. It returns - * YES if it can. It returns NO if it cannot - * (but it waits until the time limit is up before returning NO). - */ -- (BOOL) lockBeforeDate: (NSDate*)limit -{ - GSSleepInfo ctxt; - - GSSleepInit(limit, &ctxt); - while (objc_mutex_trylock(_MUTEX) == -1) - { - if (GSSleepOrFail(&ctxt) == NO) - { - return NO; - } - } - return YES; -} - -// NSLocking protocol -- (void) lock -{ - // Ask the runtime to acquire a lock on the mutex - // This will block - if (objc_mutex_lock(_MUTEX) == -1) - { - [NSException raise: NSRecursiveLockException - format: @"lock: failed to lock mutex"]; - /* NOT REACHED */ - } -} - -- (void) unlock -{ - // Ask the runtime to release a lock on the mutex - if (objc_mutex_unlock(_MUTEX) == -1) - { - [NSException raise: NSRecursiveLockException - format: @"unlock: failed to unlock mutex"]; - /* NOT REACHED */ - } -} - +NAME_METHODS @end diff --git a/Source/NSThread.m b/Source/NSThread.m index a9db1a75c..64308c5ab 100644 --- a/Source/NSThread.m +++ b/Source/NSThread.m @@ -48,9 +48,6 @@ #ifdef HAVE_PTHREAD_H #include #endif -#ifdef NeXT_RUNTIME -#include "thr-mach.h" -#endif #ifdef HAVE_SYS_FILE_H #include #endif @@ -84,6 +81,15 @@ #include #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 @@ -256,6 +262,11 @@ extern objc_mutex_t __objc_runtime_mutex; extern int __objc_runtime_threads_alive; extern int __objc_is_multi_threaded; +/* WARNING: + * GNUstep appears to have been written on the assumption that these variables + * are used correctly by the GNU runtime. In fact, they are used in only one + * place, and are used incorrectly there. + */ inline static void objc_thread_add (void) { objc_mutex_lock(__objc_runtime_mutex); @@ -263,13 +274,6 @@ inline static void objc_thread_add (void) __objc_runtime_threads_alive++; objc_mutex_unlock(__objc_runtime_mutex); } - -inline static void objc_thread_remove (void) -{ - objc_mutex_lock(__objc_runtime_mutex); - __objc_runtime_threads_alive--; - objc_mutex_unlock(__objc_runtime_mutex); -} #endif /* not HAVE_OBJC_THREAD_ADD */ /* @@ -277,87 +281,35 @@ inline static void objc_thread_remove (void) */ static BOOL entered_multi_threaded_state = NO; -/* - * Default thread. - */ -static NSThread *defaultThread = nil; +static NSLock *thread_creation_lock; /** - *

- * This function is a GNUstep extension. It pretty much - * duplicates the functionality of [NSThread +currentThread] - * but is more efficient and is used internally throughout - * GNUstep. - *

- *

- * Returns the current thread. Could perhaps return nil - * if executing a thread that was started outside the GNUstep - * environment and not registered (this should not happen in a - * well-coded application). - *

+ * Pthread cleanup call; used to free the current NSThread object when the + * thread exits. + */ +static void releaseThread(void *thread) +{ + [(NSThread*)thread release]; +} + +static pthread_key_t thread_object_key; + +/** + * These functions are serious examples of premature optimisation. */ inline NSThread* GSCurrentThread(void) { - NSThread *t; - - if (entered_multi_threaded_state == NO) - { - /* - * If the NSThread class has been initialized, we will have a default - * thread set up - otherwise we must make sure the class is initialised. - */ - if (defaultThread == nil) - { - t = [NSThread currentThread]; - } - else - { - t = defaultThread; - } - } - else - { - t = (NSThread*)objc_thread_get_data(); - if (t == nil) - { - fprintf(stderr, -"ALERT ... GSCurrentThread() ... objc_thread_get_data() call returned nil!\n" -"Your application MUST call GSRegisterCurrentThread() before attempting to\n" -"use any GNUstep code from a thread other than the main GNUstep thread.\n"); - fflush(stderr); // Needed for windoze - } - } - return t; + return [NSThread currentThread]; } - -typedef struct { @defs(NSThread) } *TInfo; - -/** - * Fast access function for thread dictionary of current thread.
- * If there is no dictionary, creates the dictionary. - */ NSMutableDictionary* GSDictionaryForThread(NSThread *t) { - if (t == nil) - { - t = GSCurrentThread(); - } - if (t == nil) - { - return nil; - } - else - { - NSMutableDictionary *dict = ((TInfo)t)->_thread_dictionary; - - if (dict == nil) + if (nil == t) { - dict = [t threadDictionary]; + t = [NSThread currentThread]; } - return dict; - } + return [t threadDictionary]; } /** @@ -410,6 +362,11 @@ gnustep_base_thread_callback(void) * Won't work properly if threads are not all created * by this class, but it's better than nothing. */ + // FIXME: This code is complete nonsense; this can be called from + // any thread (and is when adding new foreign threads), so this + // will often be called from the wrong thread, delivering + // notifications to the wrong thread, and generally doing the + // wrong thing.. if (nc == nil) { nc = RETAIN([NSNotificationCenter defaultCenter]); @@ -434,36 +391,40 @@ gnustep_base_thread_callback(void) @implementation NSThread +static void setThreadForCurrentThread(NSThread *t) +{ + pthread_setspecific(thread_object_key, t); + gnustep_base_thread_callback(); +} + + (NSArray*) callStackReturnAddresses { NSMutableArray *stack = GSPrivateStackAddresses(); return stack; } ++ (BOOL)_createThreadForCurrentPthread +{ + NSThread *t = pthread_getspecific(thread_object_key); + if (t == nil) + { + [thread_creation_lock lock]; + t = pthread_getspecific(thread_object_key); + if (t == nil) + { + t = [self new]; + pthread_setspecific(thread_object_key, t); + [thread_creation_lock unlock]; + return YES; + } + [thread_creation_lock unlock]; + } + return NO; +} + (NSThread*) currentThread { - NSThread *t = nil; - - if (entered_multi_threaded_state == NO) - { - /* - * The NSThread class has been initialized - so we will have a default - * thread set up unless the default thread subsequently exited. - */ - t = defaultThread; - } - if (t == nil) - { - t = (NSThread*)objc_thread_get_data(); - if (t == nil) - { - fprintf(stderr, "ALERT ... [NSThread +currentThread] ... the " - "objc_thread_get_data() call returned nil!"); - fflush(stderr); // Needed for windoze - } - } - return t; + return (NSThread*)pthread_getspecific(thread_object_key); } + (void) detachNewThreadSelector: (SEL)aSelector @@ -475,10 +436,9 @@ gnustep_base_thread_callback(void) /* * Create the new thread. */ - thread = (NSThread*)NSAllocateObject(self, 0, NSDefaultMallocZone()); - thread = [thread initWithTarget: aTarget - selector: aSelector - object: anArgument]; + thread = [[NSThread alloc] initWithTarget: aTarget + selector: aSelector + object: anArgument]; [thread start]; RELEASE(thread); @@ -510,23 +470,14 @@ gnustep_base_thread_callback(void) [(GSRunLoopThreadInfo*)t->_runLoopInfo invalidate]; - /* - * destroy the thread object. - */ - DESTROY(t); - - objc_thread_set_data (NULL); - #if GS_WITH_GC && defined(HAVE_GC_REGISTER_MY_THREAD) GC_unregister_my_thread(); #endif - /* - * Tell the runtime to exit the thread - */ - objc_thread_exit(); + pthread_exit(NULL); } } +static NSThread *defaultThread; /* * Class initialization */ @@ -542,15 +493,18 @@ gnustep_base_thread_callback(void) */ objc_set_thread_callback(gnustep_base_thread_callback); + if (pthread_key_create(&thread_object_key, releaseThread)) + { + [NSException raise: NSInternalInconsistencyException + format: @"Unable to create thread key!"]; + } + thread_creation_lock = [NSLock new]; /* * Ensure that the default thread exists. */ - defaultThread - = (NSThread*)NSAllocateObject(self, 0, NSDefaultMallocZone()); - defaultThread = [defaultThread init]; - defaultThread->_active = YES; - objc_thread_set_data(defaultThread); threadClass = self; + [NSThread _createThreadForCurrentPthread]; + defaultThread = [NSThread currentThread]; } } @@ -572,21 +526,26 @@ gnustep_base_thread_callback(void) /** * 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. The current gnu objc runtime supports three - * priority levels which you can obtain using values of 0.0, 0.5, and 1.0 + * system priorities. */ + (void) setThreadPriority: (double)pri { - int p; +#ifdef _POSIX_THREAD_PRIORITY_SCHEDULING + int policy; + struct sched_param param; - if (pri <= 0.3) - p = OBJC_THREAD_LOW_PRIORITY; - else if (pri <= 0.6) - p = OBJC_THREAD_BACKGROUND_PRIORITY; - else - p = OBJC_THREAD_INTERACTIVE_PRIORITY; + // Clamp pri into the required range. + if (pri > 1) { pri = 1; } + if (pri < 0) { pri = 0; } - objc_thread_set_priority(p); + // 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 @@ -612,16 +571,22 @@ gnustep_base_thread_callback(void) */ + (double) threadPriority { - int p = objc_thread_get_priority(); + 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; - if (p == OBJC_THREAD_LOW_PRIORITY) - return 0.0; - else if (p == OBJC_THREAD_BACKGROUND_PRIORITY) - return 0.5; - else if (p == OBJC_THREAD_INTERACTIVE_PRIORITY) - return 1.0; - else - return 0.0; // Unknown. } @@ -701,12 +666,6 @@ gnustep_base_thread_callback(void) _selector = aSelector; _target = RETAIN(aTarget); _arg = RETAIN(anArgument); - _thread_dictionary = nil; // Initialize this later only when needed - _exception_handler = NULL; - _cancelled = NO; - _active = NO; - _finished = NO; - _name = nil; init_autorelease_thread_vars(&_autorelease_vars); return self; } @@ -740,14 +699,38 @@ gnustep_base_thread_callback(void) NSStringFromClass([self class]), NSStringFromSelector(_cmd)]; } - if (objc_thread_get_data() != nil) - { - [NSException raise: NSInternalInconsistencyException - format: @"[%@-$@] called on running 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 GS_WITH_GC && defined(HAVE_GC_REGISTER_MY_THREAD) { struct GC_stack_base base; @@ -769,37 +752,6 @@ gnustep_base_thread_callback(void) } #endif -#if defined(HAVE_SETRLIMIT) && defined(RLIMIT_STACK) - if (_stackSize > 0) - { - struct rlimit rl; - - rl.rlim_cur = _stackSize; - rl.rlim_max = _stackSize; - if (setrlimit(RLIMIT_STACK, &rl) < 0) - { - NSDebugMLog(@"Unable to set thread stack size to %u: %@", - _stackSize, [NSError _last]); - } - } -#endif - - /* - * We are running in the new thread - so we store ourself in the thread - * dictionary and release ourself - thus, when the thread exits, we will - * be deallocated cleanly. - */ - objc_thread_set_data(self); - -#if defined(PTHREAD_JOINABLE) -/* Hack to work around the fact that - * some versions of the objective-c - * library fail to create the thread detached. - * We should really do this only in such cases. - */ -pthread_detach(pthread_self()); -#endif - /* * Let observers know a new thread is starting. */ @@ -808,35 +760,14 @@ pthread_detach(pthread_self()); nc = RETAIN([NSNotificationCenter defaultCenter]); } [nc postNotificationName: NSThreadDidStartNotification - object: self + object: t userInfo: nil]; - [_target performSelector: _selector withObject: _arg]; + [t main]; [NSThread exit]; -} - -- (NSString*) name -{ - return _name; -} - -- (void) setName: (NSString*)aName -{ - ASSIGN(_name, aName); -} - -- (void) setStackSize: (NSUInteger)stackSize -{ - _stackSize = stackSize; -#if !defined(HAVE_SETRLIMIT) || !defined(RLIMIT_STACK) - GSOnceMLog(@"Warning ... -setStackSize: not implemented on this system"); -#endif -} - -- (NSUInteger) stackSize -{ - return _stackSize; + // Not reached + return NULL; } - (void) start @@ -869,16 +800,27 @@ pthread_detach(pthread_self()); /* The thread must persist until it finishes executing. */ - IF_NO_GC(RETAIN(self);) + RETAIN(self); /* Mark the thread as active whiul it's running. */ _active = YES; errno = 0; - if (objc_thread_detach(@selector(main), self, nil) == NULL) + pthread_t thr; + pthread_attr_t attr; + 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)) { - _active = NO; RELEASE(self); [NSException raise: NSInternalInconsistencyException format: @"Unable to detach thread (last error %@)", @@ -1297,55 +1239,8 @@ GSRunLoopInfoForThread(NSThread *aThread) BOOL GSRegisterCurrentThread (void) { - NSThread *thread; - - /* - * Do nothing and return NO if the thread is known to us. - */ - if ((NSThread*)objc_thread_get_data() != nil) - { - return NO; - } - - /* - * Make sure the Objective-C runtime knows there is an additional thread. - */ - objc_thread_add (); - - if (threadClass == 0) - { - /* - * If the threadClass has not been set, NSThread has not been - * initialised, and there is no default thread. So we must - * initialise now ... which will make the current thread the default. - */ - NSCAssert(entered_multi_threaded_state == NO, - NSInternalInconsistencyException); - thread = [NSThread currentThread]; - } - else - { - /* - * Create the new thread object. - */ - thread = (NSThread*)NSAllocateObject (threadClass, 0, - NSDefaultMallocZone ()); - thread = [thread init]; - objc_thread_set_data (thread); - ((NSThread_ivars *)thread)->_active = YES; - } - - /* - * We post the notification after we register the thread. - * NB. Even if we are the default thread, we do this to register the app - * as being multi-threaded - this is so that, if this thread is unregistered - * later, it does not leave us with a bad default thread. - */ - gnustep_base_thread_callback(); - - return YES; + return [NSThread _createThreadForCurrentPthread]; } - /** *

* This function is provided to let threads started by some other @@ -1384,16 +1279,5 @@ GSUnregisterCurrentThread (void) object: thread userInfo: nil]; - /* - * destroy the thread object. - */ - DESTROY (thread); - - objc_thread_set_data (NULL); - - /* - * Make sure Objc runtime knows there is a thread less to manage - */ - objc_thread_remove (); } }