Skip to content

Commit 6774619

Browse files
authored
Execute NSOperationQueue mainQueue on main thread (#591)
1 parent 027b45f commit 6774619

3 files changed

Lines changed: 114 additions & 2 deletions

File tree

ChangeLog

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,11 @@
1+
2026-03-19 Frederik Seiffert <frederik@algoriddim.com>
2+
3+
* Source/NSOperation.m:
4+
Ensure NSOperationQueue mainQueue executes queued operations on the
5+
main thread, using libdispatch main-queue fast path when available.
6+
* Tests/base/NSOperation/threads.m:
7+
Add test for enqueuing on mainQueue from a background thread.
8+
19
2026-03-12 Richard Frith-Macdonald <rfm@gnu.org>
210

311
* Source/GSHTTPURLHandle.m:

Source/NSOperation.m

Lines changed: 69 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -66,6 +66,7 @@
6666
#import "Foundation/NSValue.h"
6767
#import "GNUstepBase/NSArray+GNUstepBase.h"
6868
#import "GSPrivate.h"
69+
#import "GSDispatch.h"
6970

7071
#define GSInternal NSOperationInternal
7172
#include "GSInternal.h"
@@ -609,6 +610,7 @@ - (void) main
609610

610611
@interface NSOperationQueue (Private)
611612
- (void) _execute;
613+
- (void) _main: (NSOperation *)op;
612614
- (void) _thread: (NSNumber *) threadNumber;
613615
- (void) observeValueForKeyPath: (NSString *)keyPath
614616
ofObject: (id)object
@@ -632,6 +634,17 @@ - (void) observeValueForKeyPath: (NSString *)keyPath
632634
static NSString *threadKey = @"NSOperationQueue";
633635
static NSOperationQueue *mainQueue = nil;
634636

637+
#if GS_USE_LIBDISPATCH == 1
638+
static void
639+
mainQueueExecuteOperation(void *context)
640+
{
641+
NSOperation *op = (NSOperation *)context;
642+
643+
[mainQueue _main: op];
644+
RELEASE(op);
645+
}
646+
#endif
647+
635648
@implementation NSOperationQueue
636649

637650
+ (id) currentQueue
@@ -648,6 +661,7 @@ + (void) initialize
648661
if (nil == mainQueue)
649662
{
650663
mainQueue = [self new];
664+
[mainQueue setMaxConcurrentOperationCount: 1];
651665
}
652666
}
653667

@@ -886,6 +900,10 @@ - (NSArray *) operations
886900

887901
- (void) setMaxConcurrentOperationCount: (NSInteger)cnt
888902
{
903+
if (self == mainQueue)
904+
{
905+
cnt = 1;
906+
}
889907
if (cnt < 0
890908
&& cnt != NSOperationQueueDefaultMaxConcurrentOperationCount)
891909
{
@@ -1102,11 +1120,35 @@ - (void) _thread: (NSNumber *) threadNumber
11021120
[NSThread exit];
11031121
}
11041122

1123+
- (void) _main: (NSOperation *)op
1124+
{
1125+
BOOL concurrent;
1126+
1127+
concurrent = [op isConcurrent];
1128+
NS_DURING
1129+
{
1130+
ENTER_POOL
1131+
[op start];
1132+
LEAVE_POOL
1133+
}
1134+
NS_HANDLER
1135+
{
1136+
NSLog(@"Problem running operation %@ ... %@",
1137+
op, localException);
1138+
}
1139+
NS_ENDHANDLER
1140+
if (NO == concurrent)
1141+
{
1142+
[op _finish];
1143+
}
1144+
}
1145+
11051146
/* Check for operations which can be executed and start them.
11061147
*/
11071148
- (void) _execute
11081149
{
11091150
NSInteger max;
1151+
NSMutableArray *mainQueueOperations = nil;
11101152

11111153
[internal->lock lock];
11121154

@@ -1136,9 +1178,17 @@ - (void) _execute
11361178
options: NSKeyValueObservingOptionNew
11371179
context: isFinishedCtxt];
11381180
internal->executing++;
1139-
if (YES == [op isConcurrent])
1181+
if (self == mainQueue)
1182+
{
1183+
if (nil == mainQueueOperations)
1184+
{
1185+
mainQueueOperations = [NSMutableArray new];
1186+
}
1187+
[mainQueueOperations addObject: op];
1188+
}
1189+
else if (YES == [op isConcurrent])
11401190
{
1141-
[op start];
1191+
[op start];
11421192
}
11431193
else
11441194
{
@@ -1178,6 +1228,23 @@ - (void) _execute
11781228
}
11791229
NS_ENDHANDLER
11801230
[internal->lock unlock];
1231+
1232+
if (nil != mainQueueOperations)
1233+
{
1234+
GS_FOR_IN(NSOperation *, op, mainQueueOperations)
1235+
{
1236+
#if GS_USE_LIBDISPATCH == 1
1237+
dispatch_async_f(dispatch_get_main_queue(), RETAIN(op),
1238+
mainQueueExecuteOperation);
1239+
#else
1240+
[self performSelectorOnMainThread: @selector(_main:)
1241+
withObject: op
1242+
waitUntilDone: NO];
1243+
#endif
1244+
}
1245+
GS_END_FOR(mainQueueOperations)
1246+
RELEASE(mainQueueOperations);
1247+
}
11811248
}
11821249

11831250
@end

Tests/base/NSOperation/threads.m

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
#import <Foundation/NSThread.h>
55
#import <Foundation/NSNotification.h>
66
#import <Foundation/NSAutoreleasePool.h>
7+
#import <Foundation/NSDate.h>
8+
#import <Foundation/NSRunLoop.h>
79
#import "ObjectTesting.h"
810

911

@@ -135,6 +137,28 @@ - (void) main
135137
}
136138
@end
137139

140+
static BOOL
141+
waitForOperation(NSOperation *op, NSTimeInterval timeout)
142+
{
143+
NSDate *end;
144+
145+
end = [NSDate dateWithTimeIntervalSinceNow: timeout];
146+
while (NO == [op isFinished] && [end timeIntervalSinceNow] > 0.0)
147+
{
148+
NSDate *next;
149+
BOOL handled;
150+
151+
next = [NSDate dateWithTimeIntervalSinceNow: 0.01];
152+
handled = [[NSRunLoop currentRunLoop] runMode: NSDefaultRunLoopMode
153+
beforeDate: next];
154+
if (NO == handled)
155+
{
156+
[NSThread sleepForTimeInterval: 0.01];
157+
}
158+
}
159+
return [op isFinished];
160+
}
161+
138162
int main()
139163
{
140164
ThreadCounter *cnt;
@@ -225,6 +249,19 @@ int main()
225249
PASS(([NSOperationQueue currentQueue] == [NSOperationQueue mainQueue]), "current queue outside -main is main queue");
226250
PASS(([NSOperationQueue mainQueue] != nil), "main queue is not nil");
227251

252+
obj = [OpFlag new];
253+
[NSThread detachNewThreadSelector: @selector(addOperation:)
254+
toTarget: [NSOperationQueue mainQueue]
255+
withObject: obj];
256+
PASS((YES == waitForOperation(obj, 2.0)),
257+
"main queue runs enqueued operations");
258+
PASS(([obj ran] == YES), "main queue operation ran");
259+
PASS(([obj thread] == [NSThread mainThread]),
260+
"main queue operation ran in main thread");
261+
PASS(([obj queue] == [NSOperationQueue mainQueue]),
262+
"main queue operation reported main queue");
263+
RELEASE(obj);
264+
228265
obj = [OpFlag new];
229266
[q addOperation: obj];
230267
[q waitUntilAllOperationsAreFinished];

0 commit comments

Comments
 (0)