diff --git a/ChangeLog b/ChangeLog index f34c7e1d1..fa32b6c3e 100644 --- a/ChangeLog +++ b/ChangeLog @@ -1,3 +1,15 @@ +2026-04-28 Hendrik Huebner + + * Source/NSOperation.m: Added libdispatch based NSOperationQueue + implementation (Enabled by default for platforms where libdispatch + is available). + * Headers/Foundation/NSOperation.h: Exposed underlyingQueue getter + and setter for platforms using libdispatch. + * Tests/base/NSOperation/queue_dispatch.m: + Added tests for underlying queue and dispatch implementation. + * Tests/base/NSOperation/GNUmakefile.preamble: Link libdispatch to + queue_dispatch.m. + 2026-04-27 Richard Frith-Macdonald * configure.ac: Add test for res_nquery() diff --git a/Headers/Foundation/NSOperation.h b/Headers/Foundation/NSOperation.h index 20fc27643..406cc8a20 100644 --- a/Headers/Foundation/NSOperation.h +++ b/Headers/Foundation/NSOperation.h @@ -25,9 +25,16 @@ #ifndef __NSOperation_h_GNUSTEP_BASE_INCLUDE #define __NSOperation_h_GNUSTEP_BASE_INCLUDE +#include "GNUstepBase/GSConfig.h" #import #if OS_API_VERSION(MAC_OS_X_VERSION_10_5, GS_API_LATEST) + +#if OS_API_VERSION(MAC_OS_X_VERSION_10_10, GS_API_LATEST) \ + && GS_USE_LIBDISPATCH == 1 +#include "dispatch/dispatch.h" +#endif + #if defined(__cplusplus) extern "C" { #endif @@ -334,9 +341,23 @@ GS_EXPORT_CLASS * and removed from the queue). */ - (void) waitUntilAllOperationsAreFinished; -@end - +#if OS_API_VERSION(MAC_OS_X_VERSION_10_10, GS_API_LATEST) \ + && GS_USE_LIBDISPATCH == 1 + /** Returns the underlying dispatch queue. + */ +- (dispatch_queue_t) underlyingQueue; + + /** Sets the underlying dispatch queue. + * + * Throws `NSInvalidArgumentException` if: + * - The argument is `NULL` + * - There are operations in the queue `(operationCount > 0)` + * - The argument is the value returned by `dispatch_get_main_queue()` + */ +- (void) setUnderlyingQueue: (dispatch_queue_t)dispatchQueue; +#endif +@end #if defined(__cplusplus) } diff --git a/Source/NSOperation.m b/Source/NSOperation.m index 26ff80000..934d61d18 100644 --- a/Source/NSOperation.m +++ b/Source/NSOperation.m @@ -25,6 +25,9 @@ */ #import "common.h" +#if GS_USE_LIBDISPATCH == 1 +#include "dispatch/dispatch.h" +#endif #import "Foundation/NSLock.h" @@ -44,16 +47,13 @@ #define GS_NSOperationQueue_IVARS \ NSRecursiveLock *lock; \ - NSConditionLock *cond; \ NSMutableArray *operations; \ NSMutableArray *waiting; \ - NSMutableArray *starting; \ NSString *name; \ - NSString *threadName; \ BOOL suspended; \ NSInteger executing; \ - NSInteger threadCount; \ - NSInteger maxThreads; + NSInteger maxThreads; \ + id queueImpl; #import "Foundation/NSOperation.h" #import "Foundation/NSArray.h" @@ -70,6 +70,7 @@ #define GSInternal NSOperationInternal #include "GSInternal.h" + GS_PRIVATE_INTERNAL(NSOperation) static void *isFinishedCtxt = (void*)"isFinished"; @@ -81,6 +82,10 @@ - (void) _finish; - (void) _updateReadyState; @end + +static const NSInteger GSOperationInitialCondition = 0; +static const NSInteger GSOperationFinishedCondition = 1; + @implementation NSOperation + (BOOL) automaticallyNotifiesObserversForKey: (NSString*)theKey @@ -243,7 +248,8 @@ - (id) init internal->lock = [NSRecursiveLock new]; [internal->lock setName: [NSString stringWithFormat: @"lock-for-opqueue-%p", self]]; - internal->cond = [[NSConditionLock alloc] initWithCondition: 0]; + internal->cond + = [[NSConditionLock alloc] initWithCondition: GSOperationInitialCondition]; [internal->cond setName: [NSString stringWithFormat: @"cond-for-opqueue-%p", self]]; [self addObserver: self @@ -318,7 +324,7 @@ - (void) observeValueForKeyPath: (NSString *)keyPath * any waiting thread can continue. */ [internal->cond lock]; - [internal->cond unlockWithCondition: 1]; + [internal->cond unlockWithCondition: GSOperationFinishedCondition]; [internal->lock unlock]; } @@ -481,8 +487,11 @@ - (double) threadPriority - (void) waitUntilFinished { - [internal->cond lockWhenCondition: 1]; // Wait for finish - [internal->cond unlockWithCondition: 1]; // Signal any other watchers + // Wait for finish + [internal->cond lockWhenCondition: GSOperationFinishedCondition]; + + // Signal any other watchers + [internal->cond unlockWithCondition: GSOperationFinishedCondition]; } @end @@ -611,20 +620,61 @@ - (void) main @interface NSOperationQueue (Private) - (void) _execute; - (void) _main: (NSOperation *)op; -- (void) _thread: (NSNumber *) threadNumber; +#if GS_USE_LIBDISPATCH == 1 +- (id) _initMainQueue; +#endif +- (NSRecursiveLock *) _internalLock; +- (NSMutableArray *) _internalOperations; +- (NSMutableArray *) _internalWaiting; +- (NSInteger *) _internalExecutingPtr; + - (void) observeValueForKeyPath: (NSString *)keyPath ofObject: (id)object change: (NSDictionary *)change context: (void *)context; @end +@interface GSOperationQueueImpl : NSObject +{ +@protected + NSOperationQueue *_queue; + NSRecursiveLock *_lock; + NSMutableArray *_operations; + NSMutableArray *_waiting; + NSInteger *_executing; +} +- (id) initWithQueue: (NSOperationQueue *)queue; +- (void) execute; +- (void) setInternalQueueName: (NSString *)name; +- (void) initAsMainQueue; +@end + +@interface GSThreadOperationQueueImpl : GSOperationQueueImpl +{ + NSConditionLock *_cond; + NSMutableArray *_starting; + NSString *_threadName; + NSInteger _threadCount; +} +@end + +#if GS_USE_LIBDISPATCH == 1 +@interface GSDispatchOperationQueueImpl : GSOperationQueueImpl +{ + dispatch_queue_t _underlyingQueue; + BOOL _ownsUnderlyingQueue; +} +- (dispatch_queue_t) underlyingQueue; +@end +#endif + static NSInteger maxConcurrent = 8; // Thread pool size static NSComparisonResult -sortFunc(id o1, id o2, void *ctxt) +compareByQueuePriority(id op1, id op2, void *ctxt) { - NSOperationQueuePriority p1 = [o1 queuePriority]; - NSOperationQueuePriority p2 = [o2 queuePriority]; + NSOperationQueuePriority p1 = [op1 queuePriority]; + NSOperationQueuePriority p2 = [op2 queuePriority]; if (p1 < p2) return NSOrderedDescending; if (p1 > p2) return NSOrderedAscending; @@ -634,14 +684,190 @@ - (void) observeValueForKeyPath: (NSString *)keyPath static NSString *threadKey = @"NSOperationQueue"; static NSOperationQueue *mainQueue = nil; +@implementation GSOperationQueueImpl + +- (void) dealloc +{ + _queue = nil; + [super dealloc]; +} + +- (id) initWithQueue: (NSOperationQueue *)queue +{ + if ((self = [super init]) != nil) + { + _queue = queue; + _lock = [_queue _internalLock]; + _operations = [_queue _internalOperations]; + _waiting = [_queue _internalWaiting]; + _executing = [_queue _internalExecutingPtr]; + } + return self; +} + +- (void) execute +{ +} + +- (void) setInternalQueueName: (NSString *)name +{ +} + +- (void) initAsMainQueue +{ +} + +@end + #if GS_USE_LIBDISPATCH == 1 +/* Function passed to dispatch_async_f to execute a NSOperation */ +static void dispatchQueueExecuteOperation(void *context); + +@implementation GSDispatchOperationQueueImpl + +- (void) dealloc +{ + if (YES == _ownsUnderlyingQueue) + { + dispatch_release(_underlyingQueue); + } + [super dealloc]; +} + +- (id) initWithQueue: (NSOperationQueue *)queue +{ + if ((self = [super initWithQueue: queue]) != nil) + { + _underlyingQueue = dispatch_queue_create( + [[queue name] UTF8String], DISPATCH_QUEUE_CONCURRENT); + _ownsUnderlyingQueue = YES; + } + return self; +} + +/* Check for operations which can be executed and start them. + */ +- (void) execute +{ + NSInteger max; + NSMutableArray *operationsToStart = nil; + NSOperationQueue *queue = _queue; + + [_lock lock]; + + max = [queue maxConcurrentOperationCount]; + if (NSOperationQueueDefaultMaxConcurrentOperationCount == max) + { + max = maxConcurrent; // Set default value + } + + NS_DURING + while (NO == [queue isSuspended] + && max > (*_executing) + && [_waiting count] > 0) + { + NSOperation *op; + + op = [_waiting objectAtIndex: 0]; + [_waiting removeObjectAtIndex: 0]; + [op removeObserver: queue forKeyPath: @"queuePriority"]; + [op addObserver: queue + forKeyPath: @"isFinished" + options: NSKeyValueObservingOptionNew + context: isFinishedCtxt]; + (*_executing)++; + if (nil == operationsToStart) + { + operationsToStart = [NSMutableArray new]; + } + [operationsToStart addObject: op]; + } + NS_HANDLER + { + [_lock unlock]; + [localException raise]; + } + NS_ENDHANDLER + [_lock unlock]; + + if (nil != operationsToStart) + { + GS_FOR_IN(NSOperation *, op, operationsToStart) + { + NSArray *context = [[NSArray alloc] initWithObjects: queue, op, nil]; + dispatch_async_f(_underlyingQueue, context, + dispatchQueueExecuteOperation); + } + GS_END_FOR(operationsToStart) + RELEASE(operationsToStart); + } +} + +- (dispatch_queue_t) underlyingQueue +{ + return _underlyingQueue; +} + +- (void) setUnderlyingQueue: (dispatch_queue_t) dispatchQueue +{ + [GSIVar(_queue, lock) lock]; + NS_DURING + { + if ([GSIVar(_queue, operations) count] > 0) + { + [NSException raise: NSInvalidArgumentException + format: @"Cannot set underlyingQueue while operations are enqueued."]; + } + if (dispatchQueue == dispatch_get_main_queue()) + { + [NSException raise: NSInvalidArgumentException + format: @"underlyingQueue must not be dispatch_get_main_queue()."]; + } + if (dispatchQueue == NULL) + { + [NSException raise: NSInvalidArgumentException + format: @"underlyingQueue must not be NULL."]; + } + + dispatch_retain(dispatchQueue); + if (YES == _ownsUnderlyingQueue && _underlyingQueue != NULL) + { + dispatch_release(_underlyingQueue); + } + _underlyingQueue = dispatchQueue; + _ownsUnderlyingQueue = YES; + } + NS_HANDLER + { + [GSIVar(_queue, lock) unlock]; + [localException raise]; + } + NS_ENDHANDLER + [GSIVar(_queue, lock) unlock]; +} + +- (void) initAsMainQueue +{ + if (YES == _ownsUnderlyingQueue && _underlyingQueue != NULL) + { + dispatch_release(_underlyingQueue); + } + _underlyingQueue = dispatch_get_main_queue(); + _ownsUnderlyingQueue = NO; +} + +@end + +/* Function passed to dispatch_async_f to execute a NSOperation */ static void -mainQueueExecuteOperation(void *context) +dispatchQueueExecuteOperation(void *context) { - NSOperation *op = (NSOperation *)context; + NSArray *a = (NSArray *)context; + NSOperationQueue *queue = (NSOperationQueue *)[a objectAtIndex: 0]; + NSOperation *op = (NSOperation *)[a objectAtIndex: 1]; - [mainQueue _main: op]; - RELEASE(op); + [queue _main: op]; + RELEASE(a); } #endif @@ -660,7 +886,7 @@ + (void) initialize { if (nil == mainQueue) { - mainQueue = [self new]; + mainQueue = [[self alloc] _initMainQueue]; [mainQueue setMaxConcurrentOperationCount: 1]; } } @@ -670,6 +896,19 @@ + (id) mainQueue return mainQueue; } +#if GS_USE_LIBDISPATCH == 1 && OS_API_VERSION(MAC_OS_X_VERSION_10_10, GS_API_LATEST) +- (dispatch_queue_t) underlyingQueue +{ + return [(GSDispatchOperationQueueImpl *)internal->queueImpl underlyingQueue]; +} + +- (void) setUnderlyingQueue: (dispatch_queue_t)dispatchQueue +{ + [(GSDispatchOperationQueueImpl *)internal->queueImpl + setUnderlyingQueue: dispatchQueue]; +} +#endif + - (void) addOperation: (NSOperation *)op { if (op == nil || NO == [op isKindOfClass: [NSOperation class]]) @@ -817,10 +1056,9 @@ - (void) dealloc { [self cancelAllOperations]; DESTROY(internal->operations); - DESTROY(internal->starting); DESTROY(internal->waiting); DESTROY(internal->name); - DESTROY(internal->cond); + DESTROY(internal->queueImpl); DESTROY(internal->lock); GS_DESTROY_INTERNAL(NSOperationQueue); } @@ -835,24 +1073,32 @@ - (id) init internal->suspended = NO; internal->maxThreads = NSOperationQueueDefaultMaxConcurrentOperationCount; internal->operations = [NSMutableArray new]; - internal->starting = [NSMutableArray new]; internal->waiting = [NSMutableArray new]; internal->lock = [NSRecursiveLock new]; [internal->lock setName: [NSString stringWithFormat: @"lock-for-op-%p", self]]; - internal->cond = [[NSConditionLock alloc] initWithCondition: 0]; - [internal->cond setName: - [NSString stringWithFormat: @"cond-for-op-%p", self]]; internal->name = [[NSString alloc] initWithFormat: @"NSOperationQueue %p", self]; +#if GS_USE_LIBDISPATCH == 1 + internal->queueImpl + = [[GSDispatchOperationQueueImpl alloc] initWithQueue: self]; +#else + internal->queueImpl + = [[GSThreadOperationQueueImpl alloc] initWithQueue: self]; +#endif + [internal->queueImpl setInternalQueueName: internal->name]; + } + return self; +} - /* Ensure that default thread name can be displayed on systems with a - * limited thread name length. - * - * This value is set to internal->name, when altered with -setName: - * Worker threads are not renamed during their lifetime. - */ - internal->threadName = @"NSOperationQ"; +- (id) _initMainQueue +{ + if ((self = [self init]) != nil) + { + [internal->lock lock]; + internal->maxThreads = 1; + [internal->queueImpl initAsMainQueue]; + [internal->lock unlock]; } return self; } @@ -932,8 +1178,7 @@ - (void) setName: (NSString*)s [self willChangeValueForKey: @"name"]; RELEASE(internal->name); internal->name = [s copy]; - // internal->threadName is unretained - internal->threadName = internal->name; + [internal->queueImpl setInternalQueueName: internal->name]; [self didChangeValueForKey: @"name"]; } [internal->lock unlock]; @@ -971,6 +1216,26 @@ - (void) waitUntilAllOperationsAreFinished @implementation NSOperationQueue (Private) +- (NSRecursiveLock *) _internalLock +{ + return internal->lock; +} + +- (NSMutableArray *) _internalOperations +{ + return internal->operations; +} + +- (NSMutableArray *) _internalWaiting +{ + return internal->waiting; +} + +- (NSInteger *) _internalExecutingPtr +{ + return &internal->executing; +} + - (void) observeValueForKeyPath: (NSString *)keyPath ofObject: (id)object change: (NSDictionary *)change @@ -1017,7 +1282,7 @@ - (void) observeValueForKeyPath: (NSString *)keyPath context: queuePriorityCtxt]; } pos = [internal->waiting insertionPosition: object - usingFunction: sortFunc + usingFunction: compareByQueuePriority context: 0]; [internal->waiting insertObject: object atIndex: pos]; [internal->lock unlock]; @@ -1025,20 +1290,99 @@ - (void) observeValueForKeyPath: (NSString *)keyPath [self _execute]; } +- (void) _main: (NSOperation *)op +{ + BOOL concurrent; +#if GS_USE_LIBDISPATCH == 1 + NSMutableDictionary *threadDictionary; +#endif + + concurrent = [op isConcurrent]; +#if GS_USE_LIBDISPATCH == 1 + threadDictionary = [[NSThread currentThread] threadDictionary]; + [threadDictionary setObject: self forKey: threadKey]; +#endif + NS_DURING + { + ENTER_POOL + [op start]; + LEAVE_POOL + } + NS_HANDLER + { + NSLog(@"Problem running operation %@ ... %@", + op, localException); + } + NS_ENDHANDLER + if (NO == concurrent) + { + [op _finish]; + } +} + +/* Check for operations which can be executed and start them. + */ +- (void) _execute +{ + [internal->queueImpl execute]; +} + +- (void) _thread: (NSNumber *) threadNumber +{ + [internal->queueImpl _thread: threadNumber]; +} + +@end + +static const NSInteger GSThreadQueueIdleCondition = 0; +static const NSInteger GSThreadQueueHasWorkCondition = 1; + +@implementation GSThreadOperationQueueImpl + +- (void) dealloc +{ + DESTROY(_starting); + DESTROY(_cond); + [super dealloc]; +} + +- (id) initWithQueue: (NSOperationQueue *)queue +{ + if ((self = [super initWithQueue: queue]) != nil) + { + _starting = [NSMutableArray new]; + _cond = [[NSConditionLock alloc] initWithCondition: + GSThreadQueueIdleCondition]; + [_cond setName: + [NSString stringWithFormat: @"cond-for-op-%p", queue]]; + + /* Ensure that default thread name can be displayed on systems with a + * limited thread name length. + * + * This value is set to internal->name, when altered with -setName: + * Worker threads are not renamed during their lifetime. + */ + _threadName = @"NSOperationQ"; + _threadCount = 0; + } + return self; +} + - (void) _thread: (NSNumber *) threadNumber { NSString *tName; NSThread *current; + NSOperationQueue *queue = _queue; CREATE_AUTORELEASE_POOL(arp); current = [NSThread currentThread]; - [internal->lock lock]; - tName = [internal->threadName stringByAppendingFormat: @"_%@", threadNumber]; - [internal->lock unlock]; + [_lock lock]; + tName = [_threadName stringByAppendingFormat: @"_%@", threadNumber]; + [_lock unlock]; - [[current threadDictionary] setObject: self forKey: threadKey]; + [[current threadDictionary] setObject: queue forKey: threadKey]; [current setName: tName]; for (;;) @@ -1046,54 +1390,56 @@ - (void) _thread: (NSNumber *) threadNumber NSOperation *op; NSDate *when; BOOL found; + /* We use a pool for each operation in case releasing the operation * causes it to be deallocated, and the deallocation of the operation * autoreleases something which needs to be cleaned up. */ RECREATE_AUTORELEASE_POOL(arp); + /* Wait up to five seconds for work to be added to `_starting`. */ when = [[NSDate alloc] initWithTimeIntervalSinceNow: 5.0]; - found = [internal->cond lockWhenCondition: 1 beforeDate: when]; + found = [_cond lockWhenCondition: GSThreadQueueHasWorkCondition + beforeDate: when]; RELEASE(when); if (NO == found) { - [internal->cond lock]; - if ([internal->starting count] == 0) + [_cond lock]; + if ([_starting count] == 0) { - // Idle for 5 seconds ... exit thread. - [internal->cond unlock]; + /* Still no work after timeout: remove queue mapping and exit. */ + [_cond unlock]; [[[NSThread currentThread] threadDictionary] removeObjectForKey: threadKey]; - [internal->lock lock]; - internal->threadCount--; - [internal->lock unlock]; + [_lock lock]; + _threadCount--; + [_lock unlock]; break; } - /* An operation was added in the gap between the failed wait for + + /* An operation was added in the gap between the failed wait for * the condition and us unconditionally locking the condition, so * we fall through to execute that operation. */ } - if ([internal->starting count] > 0) + if ([_starting count] > 0) { - op = RETAIN([internal->starting objectAtIndex: 0]); - [internal->starting removeObjectAtIndex: 0]; + op = RETAIN([_starting objectAtIndex: 0]); + [_starting removeObjectAtIndex: 0]; } else { op = nil; } - if ([internal->starting count] > 0) + if ([_starting count] > 0) { - // Signal any other idle threads, - [internal->cond unlockWithCondition: 1]; + [_cond unlockWithCondition: GSThreadQueueHasWorkCondition]; } else { - // There are no more operations starting. - [internal->cond unlockWithCondition: 0]; + [_cond unlockWithCondition: GSThreadQueueIdleCondition]; } if (nil != op) @@ -1101,6 +1447,9 @@ - (void) _thread: (NSNumber *) threadNumber NS_DURING { ENTER_POOL + /* Execute on this worker thread using the operation's + * configured thread priority. + */ [NSThread setThreadPriority: [op threadPriority]]; [op start]; LEAVE_POOL @@ -1120,48 +1469,26 @@ - (void) _thread: (NSNumber *) threadNumber [NSThread exit]; } -- (void) _main: (NSOperation *)op -{ - BOOL concurrent; - - concurrent = [op isConcurrent]; - NS_DURING - { - ENTER_POOL - [op start]; - LEAVE_POOL - } - NS_HANDLER - { - NSLog(@"Problem running operation %@ ... %@", - op, localException); - } - NS_ENDHANDLER - if (NO == concurrent) - { - [op _finish]; - } -} - /* Check for operations which can be executed and start them. */ -- (void) _execute +- (void) execute { NSInteger max; NSMutableArray *mainQueueOperations = nil; + NSOperationQueue *queue = _queue; - [internal->lock lock]; + [_lock lock]; - max = [self maxConcurrentOperationCount]; + max = [queue maxConcurrentOperationCount]; if (NSOperationQueueDefaultMaxConcurrentOperationCount == max) { max = maxConcurrent; } NS_DURING - while (NO == [self isSuspended] - && max > internal->executing - && [internal->waiting count] > 0) + while (NO == [queue isSuspended] + && max > (*_executing) + && [_waiting count] > 0) { NSOperation *op; @@ -1170,15 +1497,15 @@ - (void) _execute * and we keep track of the count of operations we have started, * but the actual startup is left to the NSOperation -start method. */ - op = [internal->waiting objectAtIndex: 0]; - [internal->waiting removeObjectAtIndex: 0]; - [op removeObserver: self forKeyPath: @"queuePriority"]; - [op addObserver: self + op = [_waiting objectAtIndex: 0]; + [_waiting removeObjectAtIndex: 0]; + [op removeObserver: queue forKeyPath: @"queuePriority"]; + [op addObserver: queue forKeyPath: @"isFinished" options: NSKeyValueObservingOptionNew context: isFinishedCtxt]; - internal->executing++; - if (self == mainQueue) + (*_executing)++; + if (queue == mainQueue) { if (nil == mainQueueOperations) { @@ -1192,60 +1519,58 @@ - (void) _execute } else { - [internal->cond lock]; - [internal->starting addObject: op]; + [_cond lock]; + [_starting addObject: op]; /* Create a new thread if all existing threads are busy and * we haven't reached the pool limit. */ - if (internal->threadCount < max) + if (_threadCount < max) { - NSInteger count = internal->threadCount++; + NSInteger count = _threadCount++; NSNumber *threadNumber = [NSNumber numberWithInteger: count]; NS_DURING { [NSThread detachNewThreadSelector: @selector(_thread:) - toTarget: self + toTarget: queue withObject: threadNumber]; } NS_HANDLER { NSLog(@"Failed to create thread %@ for %@: %@", - threadNumber, self, localException); + threadNumber, queue, localException); + --_threadCount; } NS_ENDHANDLER } - /* Tell the thread pool that there is an operation to start. - */ - [internal->cond unlockWithCondition: 1]; + [_cond unlockWithCondition: GSThreadQueueHasWorkCondition]; } } NS_HANDLER { - [internal->lock unlock]; + [_lock unlock]; [localException raise]; } NS_ENDHANDLER - [internal->lock unlock]; + [_lock unlock]; if (nil != mainQueueOperations) { GS_FOR_IN(NSOperation *, op, mainQueueOperations) { -#if GS_USE_LIBDISPATCH == 1 - dispatch_async_f(dispatch_get_main_queue(), RETAIN(op), - mainQueueExecuteOperation); -#else - [self performSelectorOnMainThread: @selector(_main:) - withObject: op - waitUntilDone: NO]; -#endif + [queue performSelectorOnMainThread: @selector(_main:) + withObject: op + waitUntilDone: NO]; } GS_END_FOR(mainQueueOperations) RELEASE(mainQueueOperations); } } -@end +- (void) setInternalQueueName: (NSString *)name +{ + _threadName = name; +} +@end diff --git a/Tests/base/NSOperation/GNUmakefile.preamble b/Tests/base/NSOperation/GNUmakefile.preamble new file mode 100644 index 000000000..1587fb6bd --- /dev/null +++ b/Tests/base/NSOperation/GNUmakefile.preamble @@ -0,0 +1,4 @@ +include ../../../config.mak +ifeq ($(GNUSTEP_BASE_HAVE_LIBDISPATCH),1) +queue_dispatch_TOOL_LIBS += -ldispatch +endif diff --git a/Tests/base/NSOperation/queue_dispatch.m b/Tests/base/NSOperation/queue_dispatch.m new file mode 100644 index 000000000..a412fa74b --- /dev/null +++ b/Tests/base/NSOperation/queue_dispatch.m @@ -0,0 +1,157 @@ +#import +#import +#import +#import +#import +#if GS_USE_LIBDISPATCH == 1 +#import +#endif +#import "ObjectTesting.h" + +@interface Counter : NSObject +{ + NSLock *_lock; + NSUInteger _value; +} +- (void) increment; +- (NSUInteger) value; +@end + +@implementation Counter +- (id) init +{ + self = [super init]; + if (self != nil) + { + _lock = [NSLock new]; + _value = 0; + } + return self; +} + +- (void) dealloc +{ + [_lock release]; + [super dealloc]; +} + +- (void) increment +{ + [_lock lock]; + _value++; + [_lock unlock]; +} + +- (NSUInteger) value +{ + NSUInteger v; + + [_lock lock]; + v = _value; + [_lock unlock]; + return v; +} +@end + +@interface DelayIncrementOperation : NSOperation +{ + Counter *_counter; + NSTimeInterval _delay; +} +- (id) initWithCounter: (Counter *)counter delay: (NSTimeInterval)delay; +@end + +@implementation DelayIncrementOperation +- (id) initWithCounter: (Counter *)counter delay: (NSTimeInterval)delay +{ + self = [super init]; + if (self != nil) + { + _counter = [counter retain]; + _delay = delay; + } + return self; +} + +- (void) dealloc +{ + [_counter release]; + [super dealloc]; +} + +- (void) main +{ + if (_delay > 0.0) + { + [NSThread sleepForTimeInterval: _delay]; + } + [_counter increment]; +} +@end + +@interface NSOperationQueue (QueueDispatchTest) +- (void *) underlyingQueue; +- (void) setUnderlyingQueue: (void *)queue; +@end + +int main() +{ + NSOperationQueue *queue; + START_SET("NSOperationQueue dispatch-backed behavior") + +#if GS_USE_LIBDISPATCH == 1 + { + void *oldUnderlying; + void *customUnderlying; + + queue = [NSOperationQueue new]; + oldUnderlying = [queue underlyingQueue]; + PASS(([queue underlyingQueue] != NULL), + "custom operation queue exposes a non-null underlying queue"); + PASS(([[NSOperationQueue mainQueue] underlyingQueue] != NULL), + "main queue exposes a non-null underlying queue"); + PASS(([queue underlyingQueue] != [[NSOperationQueue mainQueue] underlyingQueue]), + "custom queue and main queue use different underlying queues"); + + // Assigning a custom libdispatch queue succeeds and updates the value. + customUnderlying = dispatch_queue_create("queue-dispatch-test", NULL); + [queue setUnderlyingQueue: customUnderlying]; + PASS(([queue underlyingQueue] == customUnderlying), + "setUnderlyingQueue accepts a non-main dispatch queue"); + PASS(([queue underlyingQueue] != oldUnderlying), + "setUnderlyingQueue replaces the previous underlying queue"); + + // Assigning the main queue must fail. + PASS_EXCEPTION( + [queue setUnderlyingQueue: dispatch_get_main_queue()], + NSInvalidArgumentException, + "setUnderlyingQueue rejects dispatch_get_main_queue"); + + // Assigning while operations are enqueued must fail. + { + Counter *busyCounter; + DelayIncrementOperation *op; + + busyCounter = [Counter new]; + op = [[DelayIncrementOperation alloc] initWithCounter: busyCounter + delay: 0.20]; + [queue addOperation: op]; + [op release]; + PASS_EXCEPTION( + [queue setUnderlyingQueue: customUnderlying], + NSInvalidArgumentException, + "setUnderlyingQueue rejects changes while operations are enqueued"); + [queue waitUntilAllOperationsAreFinished]; + [busyCounter release]; + } + + dispatch_release((dispatch_queue_t)customUnderlying); + [queue release]; + } +#else + PASS(YES, "underlyingQueue checks are disabled when libdispatch is unavailable"); +#endif + + END_SET("NSOperationQueue dispatch-backed behavior"); + return 0; +}