/** Mutual exclusion locking classes Copyright (C) 1996 Free Software Foundation, Inc. Author: Scott Christley Created: 1996 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 Library 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 Library General Public License along with this library; if not, write to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111 USA. NSLock class reference $Date$ $Revision$ */ #include #include #ifdef HAVE_UNISTD_H #include #endif #include #include #include #include // 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 it's 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 aquired 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 gcFinalize]; [super dealloc]; } - (void) gcFinalize { if (_mutex != 0) { // Ask the runtime to deallocate the mutex // If there are outstanding locks then it will block if (objc_mutex_deallocate(_mutex) == -1) { NSWarnMLog(@"objc_mutex_deallocate() failed"); } } } /** * Attempts to aquire a lock, but returns immediately if the lock * cannot be aquired. It returns YES if the lock is aquired. It returns * NO if the lock cannot be aquired 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 aquire 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; /* 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) { NSDate *current = [NSDate date]; NSComparisonResult compare; compare = [current compare: limit]; if (compare == NSOrderedSame || compare == NSOrderedDescending) { return NO; } /* * This should probably be more accurate like usleep(250) * but usleep is known to NOT be thread safe under all architectures. */ sleep(1); } return YES; } /** * Attempts to aquire 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 */ } } @end // NSConditionLock // Allows locking and unlocking to be based upon an integer condition @implementation NSConditionLock - (id) init { return [self initWithCondition: 0]; } // Designated initializer // Initialize lock with condition - (id) initWithCondition: (int)value { self = [super init]; if (self != nil) { _condition_value = value; // Allocate the mutex from the runtime _condition = objc_condition_allocate (); if (_condition == 0) { NSLog(@"Failed to allocate a condition"); RELEASE(self); return nil; } _mutex = objc_mutex_allocate (); if (_mutex == 0) { NSLog(@"Failed to allocate a mutex"); RELEASE(self); return nil; } } return self; } - (void) dealloc { [self gcFinalize]; [super dealloc]; } - (void) gcFinalize { if (_condition != 0) { // Ask the runtime to deallocate the condition if (objc_condition_deallocate(_condition) == -1) { NSWarnMLog(@"objc_condition_deallocate() failed"); } } if (_mutex != 0) { // Ask the runtime to deallocate the mutex // If there are outstanding locks then it will block if (objc_mutex_deallocate(_mutex) == -1) { NSWarnMLog(@"objc_mutex_deallocate() failed"); } } } // Return the current condition of the lock - (int) condition { return _condition_value; } // Acquiring and release the lock - (void) lockWhenCondition: (int)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 */ } } } - (void) unlockWithCondition: (int)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 { CHECK_RECURSIVE_CONDITION_LOCK(_mutex); // Ask the runtime to acquire a lock on the mutex if (objc_mutex_trylock(_mutex) == -1) return NO; else return YES; } - (BOOL) tryLockWhenCondition: (int)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; } } // Acquiring the lock with a date condition - (BOOL) lockBeforeDate: (NSDate*)limit { CHECK_RECURSIVE_CONDITION_LOCK(_mutex); while (objc_mutex_trylock(_mutex) == -1) { NSDate *current = [NSDate date]; NSComparisonResult compare; compare = [current compare: limit]; if (compare == NSOrderedSame || compare == NSOrderedDescending) { return NO; } /* * This should probably be more accurate like usleep(250) * but usleep is known to NOT be thread safe under all architectures. */ sleep(1); } return YES; } - (BOOL) lockWhenCondition: (int)condition_to_meet beforeDate: (NSDate*)limitDate { #ifndef HAVE_OBJC_CONDITION_TIMEDWAIT [self notImplemented: _cmd]; 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 =(unsigned int)atimeinterval; // 941883028;// endtime.tv_nsec = (unsigned int)((atimeinterval - (float)endtime.tv_sec) * 1000000000.0); while (_condition_value != condition_to_meet) { switch (objc_condition_timedwait(_condition, _mutex, &endtime)) { 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; #endif /* HAVE__OBJC_CONDITION_TIMEDWAIT */ } // NSLocking protocol // These methods ignore the condition - (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 */ } } - (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 */ } } @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 cooresponding unlock, and the * lock is not released for another thread to aquire 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 gcFinalize]; [super dealloc]; } - (void) gcFinalize { if (_mutex != 0) { // Ask the runtime to deallocate the mutex // If there are outstanding locks then it will block if (objc_mutex_deallocate(_mutex) == -1) { NSWarnMLog(@"objc_mutex_deallocate() failed"); } } } /** * Attempts to aquire a lock, but returns NO immediately if the lock * cannot be aquired. It returns YES if the lock is aquired. 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; } /** * Attempts to aquire 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 { while (objc_mutex_trylock(_mutex) == -1) { NSDate *current = [NSDate date]; NSComparisonResult compare; compare = [current compare: limit]; if (compare == NSOrderedSame || compare == NSOrderedDescending) { return NO; } /* * This should probably be more accurate like usleep(250) * but usleep is known to NOT be thread safe under all architectures. */ sleep(1); } 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 */ } } @end