Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions ChangeLog
Original file line number Diff line number Diff line change
@@ -1,3 +1,11 @@
2026-03-19 Frederik Seiffert <frederik@algoriddim.com>

* Source/NSOperation.m:
Ensure NSOperationQueue mainQueue executes queued operations on the
main thread, using libdispatch main-queue fast path when available.
* Tests/base/NSOperation/threads.m:
Add test for enqueuing on mainQueue from a background thread.

2026-03-12 Richard Frith-Macdonald <rfm@gnu.org>

* Source/GSHTTPURLHandle.m:
Expand Down
71 changes: 69 additions & 2 deletions Source/NSOperation.m
Original file line number Diff line number Diff line change
Expand Up @@ -66,6 +66,7 @@
#import "Foundation/NSValue.h"
#import "GNUstepBase/NSArray+GNUstepBase.h"
#import "GSPrivate.h"
#import "GSDispatch.h"

#define GSInternal NSOperationInternal
#include "GSInternal.h"
Expand Down Expand Up @@ -609,6 +610,7 @@ - (void) main

@interface NSOperationQueue (Private)
- (void) _execute;
- (void) _main: (NSOperation *)op;
- (void) _thread: (NSNumber *) threadNumber;
- (void) observeValueForKeyPath: (NSString *)keyPath
ofObject: (id)object
Expand All @@ -632,6 +634,17 @@ - (void) observeValueForKeyPath: (NSString *)keyPath
static NSString *threadKey = @"NSOperationQueue";
static NSOperationQueue *mainQueue = nil;

#if GS_USE_LIBDISPATCH == 1
static void
mainQueueExecuteOperation(void *context)
{
NSOperation *op = (NSOperation *)context;

[mainQueue _main: op];
RELEASE(op);
}
#endif

@implementation NSOperationQueue

+ (id) currentQueue
Expand All @@ -648,6 +661,7 @@ + (void) initialize
if (nil == mainQueue)
{
mainQueue = [self new];
[mainQueue setMaxConcurrentOperationCount: 1];
}
}

Expand Down Expand Up @@ -886,6 +900,10 @@ - (NSArray *) operations

- (void) setMaxConcurrentOperationCount: (NSInteger)cnt
{
if (self == mainQueue)
{
cnt = 1;
}
if (cnt < 0
&& cnt != NSOperationQueueDefaultMaxConcurrentOperationCount)
{
Expand Down Expand Up @@ -1102,11 +1120,35 @@ - (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
{
NSInteger max;
NSMutableArray *mainQueueOperations = nil;

[internal->lock lock];

Expand Down Expand Up @@ -1136,9 +1178,17 @@ - (void) _execute
options: NSKeyValueObservingOptionNew
context: isFinishedCtxt];
internal->executing++;
if (YES == [op isConcurrent])
if (self == mainQueue)
{
if (nil == mainQueueOperations)
{
mainQueueOperations = [NSMutableArray new];
}
[mainQueueOperations addObject: op];
}
else if (YES == [op isConcurrent])
{
[op start];
[op start];
}
else
{
Expand Down Expand Up @@ -1178,6 +1228,23 @@ - (void) _execute
}
NS_ENDHANDLER
[internal->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
}
GS_END_FOR(mainQueueOperations)
RELEASE(mainQueueOperations);
}
}

@end
Expand Down
37 changes: 37 additions & 0 deletions Tests/base/NSOperation/threads.m
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@
#import <Foundation/NSThread.h>
#import <Foundation/NSNotification.h>
#import <Foundation/NSAutoreleasePool.h>
#import <Foundation/NSDate.h>
#import <Foundation/NSRunLoop.h>
#import "ObjectTesting.h"


Expand Down Expand Up @@ -135,6 +137,28 @@ - (void) main
}
@end

static BOOL
waitForOperation(NSOperation *op, NSTimeInterval timeout)
{
NSDate *end;

end = [NSDate dateWithTimeIntervalSinceNow: timeout];
while (NO == [op isFinished] && [end timeIntervalSinceNow] > 0.0)
{
NSDate *next;
BOOL handled;

next = [NSDate dateWithTimeIntervalSinceNow: 0.01];
handled = [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
beforeDate: next];
if (NO == handled)
{
[NSThread sleepForTimeInterval: 0.01];
}
}
return [op isFinished];
}

int main()
{
ThreadCounter *cnt;
Expand Down Expand Up @@ -225,6 +249,19 @@ int main()
PASS(([NSOperationQueue currentQueue] == [NSOperationQueue mainQueue]), "current queue outside -main is main queue");
PASS(([NSOperationQueue mainQueue] != nil), "main queue is not nil");

obj = [OpFlag new];
[NSThread detachNewThreadSelector: @selector(addOperation:)
toTarget: [NSOperationQueue mainQueue]
withObject: obj];
PASS((YES == waitForOperation(obj, 2.0)),
"main queue runs enqueued operations");
PASS(([obj ran] == YES), "main queue operation ran");
PASS(([obj thread] == [NSThread mainThread]),
"main queue operation ran in main thread");
PASS(([obj queue] == [NSOperationQueue mainQueue]),
"main queue operation reported main queue");
RELEASE(obj);

obj = [OpFlag new];
[q addOperation: obj];
[q waitUntilAllOperationsAreFinished];
Expand Down
Loading