Line data Source code
1 0 : /*
2 :
3 : OOAsyncWorkManager.m
4 :
5 :
6 : Copyright (C) 2009-2013 Jens Ayton
7 :
8 : Permission is hereby granted, free of charge, to any person obtaining a copy
9 : of this software and associated documentation files (the "Software"), to deal
10 : in the Software without restriction, including without limitation the rights
11 : to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
12 : copies of the Software, and to permit persons to whom the Software is
13 : furnished to do so, subject to the following conditions:
14 :
15 : The above copyright notice and this permission notice shall be included in all
16 : copies or substantial portions of the Software.
17 :
18 : THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
19 : IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
20 : FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
21 : AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
22 : LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
23 : OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
24 : SOFTWARE.
25 :
26 : */
27 :
28 : #import "OOAsyncWorkManager.h"
29 : #import "OOAsyncQueue.h"
30 : #import "OOCPUInfo.h"
31 : #import "OOCollectionExtractors.h"
32 : #import "NSThreadOOExtensions.h"
33 : #import "OONSOperation.h"
34 :
35 0 : #define USE_PTHREAD_ONCE (!OOLITE_WINDOWS)
36 :
37 : #if USE_PTHREAD_ONCE
38 : #include <pthread.h>
39 : #endif
40 :
41 :
42 0 : static OOAsyncWorkManager *sSingleton = nil;
43 :
44 :
45 : @interface NSThread (MethodsThatMayExistDependingOnSystem)
46 :
47 0 : - (BOOL) isMainThread;
48 0 : + (BOOL) isMainThread;
49 :
50 : @end
51 :
52 :
53 : /* OOAsyncWorkManagerInternal: shared superclass of our two implementations,
54 : which implements shared functionality but is not itself concrete.
55 : */
56 0 : @interface OOAsyncWorkManagerInternal: OOAsyncWorkManager
57 : {
58 : @private
59 0 : OOAsyncQueue *_readyQueue;
60 :
61 0 : NSMutableSet *_pendingCompletableOperations;
62 0 : NSLock *_pendingOpsLock;
63 : }
64 :
65 0 : - (void) queueResult:(id<OOAsyncWorkTask>)task;
66 :
67 0 : - (void) noteTaskQueued:(id<OOAsyncWorkTask>)task;
68 :
69 : @end
70 :
71 :
72 : #if !OO_HAVE_NSOPERATION
73 : @interface OOManualDispatchAsyncWorkManager: OOAsyncWorkManagerInternal
74 : {
75 : @private
76 : OOAsyncQueue *_taskQueue;
77 : }
78 :
79 : - (void) queueTask:(NSNumber *)threadNumber;
80 :
81 : @end
82 : #endif
83 :
84 :
85 0 : @interface OOOperationQueueAsyncWorkManager: OOAsyncWorkManagerInternal
86 : {
87 : @private
88 0 : OONSOperationQueue _operationQueue;
89 : }
90 :
91 : #if !OO_HAVE_NSOPERATION
92 : + (BOOL) canBeUsed;
93 : #endif
94 :
95 0 : - (void) dispatchTask:(id<OOAsyncWorkTask>)task;
96 :
97 : @end
98 :
99 :
100 : #if !USE_PTHREAD_ONCE
101 : static NSLock *sInitLock = nil;
102 : #endif
103 :
104 :
105 0 : static void InitAsyncWorkManager(void)
106 : {
107 : NSCAssert(sSingleton == nil, @"Async Work Manager singleton not nil in one-time init");
108 :
109 : #if !OO_HAVE_NSOPERATION
110 : if ([OOOperationQueueAsyncWorkManager canBeUsed])
111 : {
112 : sSingleton = [[OOOperationQueueAsyncWorkManager alloc] init];
113 : }
114 : if (sSingleton == nil)
115 : {
116 : sSingleton = [[OOManualDispatchAsyncWorkManager alloc] init];
117 : }
118 : #else
119 : sSingleton = [[OOOperationQueueAsyncWorkManager alloc] init];
120 : #endif
121 :
122 : if (sSingleton == nil)
123 : {
124 : OOLog(@"asyncWorkManager.setUpDispatcher.failed", @"%@", @"***** FATAL ERROR: could not set up async work manager!");
125 : exit(EXIT_FAILURE);
126 : }
127 :
128 : OOLog(@"asyncWorkManager.dispatchMethod", @"Selected async work manager: %@", [sSingleton class]);
129 : }
130 :
131 :
132 : @implementation OOAsyncWorkManager
133 :
134 : #if !USE_PTHREAD_ONCE
135 : + (void) initialize
136 : {
137 : if (sInitLock == nil)
138 : {
139 : sInitLock = [[NSLock alloc] init];
140 : NSAssert(sInitLock != nil, @"Async Work Manager init failed");
141 : }
142 : }
143 : #endif
144 :
145 :
146 : + (OOAsyncWorkManager *) sharedAsyncWorkManager
147 : {
148 : #if USE_PTHREAD_ONCE
149 : static pthread_once_t once = PTHREAD_ONCE_INIT;
150 : pthread_once(&once, InitAsyncWorkManager);
151 : NSAssert(sSingleton != nil, @"Async Work Manager init failed");
152 : #else
153 : [sInitLock lock];
154 : if (sSingleton == nil)
155 : {
156 : InitAsyncWorkManager();
157 : NSAssert(sSingleton != nil, @"Async Work Manager init failed");
158 : }
159 : [sInitLock unlock];
160 : #endif
161 :
162 : return sSingleton;
163 : }
164 :
165 :
166 0 : + (id) allocWithZone:(NSZone *)inZone
167 : {
168 : if (sSingleton == nil)
169 : {
170 : sSingleton = [super allocWithZone:inZone];
171 : return sSingleton;
172 : }
173 : return nil;
174 : }
175 :
176 :
177 0 : - (void) dealloc
178 : {
179 : abort();
180 : [super dealloc];
181 : }
182 :
183 :
184 0 : - (oneway void) release
185 : {}
186 :
187 :
188 0 : - (id) retain
189 : {
190 : return self;
191 : }
192 :
193 :
194 0 : - (NSUInteger) retainCount
195 : {
196 : return UINT_MAX;
197 : }
198 :
199 :
200 : - (BOOL) addTask:(id<OOAsyncWorkTask>)task priority:(OOAsyncWorkPriority)priority
201 : {
202 : OOLogGenericSubclassResponsibility();
203 : return NO;
204 : }
205 :
206 :
207 : - (void) completePendingTasks
208 : {
209 : OOLogGenericSubclassResponsibility();
210 : }
211 :
212 :
213 : - (void) waitForTaskToComplete:(id<OOAsyncWorkTask>)task
214 : {
215 : OOLogGenericSubclassResponsibility();
216 : [NSException raise:NSInternalInconsistencyException format:@"%s called.", __PRETTY_FUNCTION__];
217 : }
218 :
219 : @end
220 :
221 :
222 : @implementation OOAsyncWorkManagerInternal
223 :
224 :
225 0 : - (id) init
226 : {
227 : if ((self = [super init]))
228 : {
229 : _readyQueue = [[OOAsyncQueue alloc] init];
230 :
231 : if (_readyQueue == nil)
232 : {
233 : [self release];
234 : return nil;
235 : }
236 :
237 : _pendingCompletableOperations = [[NSMutableSet alloc] init];
238 : _pendingOpsLock = [[NSLock alloc] init];
239 :
240 : if (_pendingCompletableOperations == nil || _pendingOpsLock == nil)
241 : {
242 : [self release];
243 : return nil;
244 : }
245 : }
246 :
247 : return self;
248 : }
249 :
250 :
251 0 : - (void) completePendingTasks
252 : {
253 : id next = nil;
254 :
255 : [_pendingOpsLock lock];
256 : for (;;)
257 : {
258 : next = [_readyQueue tryDequeue];
259 : if (next == nil) break;
260 :
261 : [_pendingCompletableOperations removeObject:next];
262 : [next completeAsyncTask];
263 : }
264 : [_pendingOpsLock unlock];
265 : }
266 :
267 :
268 0 : - (void) waitForTaskToComplete:(id<OOAsyncWorkTask>)task
269 : {
270 : if (task == nil) return;
271 :
272 : #if OO_DEBUG
273 : NSParameterAssert([(id)task respondsToSelector:@selector(completeAsyncTask)]);
274 : NSAssert1(![NSThread respondsToSelector:@selector(isMainThread)] || [[NSThread self] isMainThread], @"%s can only be called from the main thread.", __PRETTY_FUNCTION__);
275 : #endif
276 :
277 : [_pendingOpsLock lock];
278 : BOOL exists = [_pendingCompletableOperations containsObject:task];
279 : if (exists) [_pendingCompletableOperations removeObject:task];
280 : [_pendingOpsLock unlock];
281 :
282 : if (!exists) return;
283 :
284 : id next = nil;
285 : do
286 : {
287 : // Dequeue a task and complete it.
288 : next = [_readyQueue dequeue];
289 : [_pendingOpsLock lock];
290 : [_pendingCompletableOperations removeObject:next];
291 : [_pendingOpsLock unlock];
292 :
293 : [next completeAsyncTask];
294 :
295 : } while (next != task); // We don't control order, so keep looking until we get the one we care about.
296 : }
297 :
298 :
299 : - (void) queueResult:(id<OOAsyncWorkTask>)task
300 : {
301 : if ([task respondsToSelector:@selector(completeAsyncTask)])
302 : {
303 : [_readyQueue enqueue:task];
304 : }
305 : }
306 :
307 :
308 : - (void) noteTaskQueued:(id<OOAsyncWorkTask>)task
309 : {
310 : [_pendingOpsLock lock];
311 : [_pendingCompletableOperations addObject:task];
312 : [_pendingOpsLock unlock];
313 : }
314 :
315 : @end
316 :
317 :
318 :
319 : /******* OOManualDispatchAsyncWorkManager - manual thread management *******/
320 :
321 0 : enum
322 : {
323 : kMaxWorkThreads = 8
324 : };
325 :
326 :
327 : #if !OO_HAVE_NSOPERATION
328 : @implementation OOManualDispatchAsyncWorkManager
329 :
330 : - (id) init
331 : {
332 : if ((self = [super init]))
333 : {
334 : // Set up work queue.
335 : _taskQueue = [[OOAsyncQueue alloc] init];
336 : if (_taskQueue == nil)
337 : {
338 : [self release];
339 : return nil;
340 : }
341 :
342 : // Set up loading threads.
343 : NSUInteger threadCount, threadNumber = 1;
344 : #if OO_DEBUG
345 : threadCount = kMaxWorkThreads;
346 : #else
347 : threadCount = MIN(OOCPUCount(), (unsigned)kMaxWorkThreads);
348 : #endif
349 : do
350 : {
351 : [NSThread detachNewThreadSelector:@selector(queueTask:) toTarget:self withObject:[NSNumber numberWithInt:threadNumber++]];
352 : } while (--threadCount > 0);
353 : }
354 :
355 : return self;
356 : }
357 :
358 :
359 : - (BOOL) addTask:(id<OOAsyncWorkTask>)task priority:(OOAsyncWorkPriority)priority
360 : {
361 : if (EXPECT_NOT(task == nil)) return NO;
362 :
363 : [super noteTaskQueued:task];
364 :
365 : // Priority is ignored.
366 : return [_taskQueue enqueue:task];
367 : }
368 :
369 :
370 : - (void) queueTask:(NSNumber *)threadNumber
371 : {
372 : NSAutoreleasePool *rootPool = nil, *pool = nil;
373 :
374 : rootPool = [[NSAutoreleasePool alloc] init];
375 :
376 : [NSThread setThreadPriority:0.5];
377 : [NSThread ooSetCurrentThreadName:[NSString stringWithFormat:@"OOAsyncWorkManager thread %@", threadNumber]];
378 :
379 : for (;;)
380 : {
381 : pool = [[NSAutoreleasePool alloc] init];
382 :
383 : id<OOAsyncWorkTask> task = [_taskQueue dequeue];
384 : @try
385 : {
386 : [task performAsyncTask];
387 : }
388 : @catch (id exception) {}
389 : [self queueResult:task];
390 :
391 : [pool release];
392 : }
393 :
394 : [rootPool release];
395 : }
396 :
397 : @end
398 : #endif
399 :
400 :
401 : /******* OOOperationQueueAsyncWorkManager - dispatch through NSOperationQueue if available *******/
402 :
403 :
404 : @implementation OOOperationQueueAsyncWorkManager
405 :
406 : #if !OO_HAVE_NSOPERATION
407 : + (BOOL) canBeUsed
408 : {
409 : if ([[NSUserDefaults standardUserDefaults] boolForKey:@"disable-operation-queue-work-manager"]) return NO;
410 : return [OONSInvocationOperationClass() class] != Nil;
411 : }
412 : #endif
413 :
414 :
415 0 : - (id) init
416 : {
417 : if ((self = [super init]))
418 : {
419 : _operationQueue = [[OONSOperationQueueClass() alloc] init];
420 :
421 : if (_operationQueue == nil)
422 : {
423 : [self release];
424 : return nil;
425 : }
426 : }
427 :
428 : return self;
429 : }
430 :
431 :
432 0 : - (void) dealloc
433 : {
434 : [_operationQueue release];
435 :
436 : [super dealloc];
437 : }
438 :
439 :
440 0 : - (BOOL) addTask:(id<OOAsyncWorkTask>)task priority:(OOAsyncWorkPriority)priority
441 : {
442 : if (EXPECT_NOT(task == nil)) return NO;
443 :
444 : id operation = [[OONSInvocationOperationClass() alloc] initWithTarget:self selector:@selector(dispatchTask:) object:task];
445 : if (operation == nil) return NO;
446 :
447 : if (priority == kOOAsyncPriorityLow) [operation setQueuePriority:OONSOperationQueuePriorityLow];
448 : else if (priority == kOOAsyncPriorityHigh) [operation setQueuePriority:OONSOperationQueuePriorityHigh];
449 :
450 : [_operationQueue addOperation:operation];
451 : [operation release];
452 :
453 : [super noteTaskQueued:task];
454 : return YES;
455 : }
456 :
457 :
458 : - (void) dispatchTask:(id<OOAsyncWorkTask>)task
459 : {
460 : @try
461 : {
462 : [task performAsyncTask];
463 : }
464 : @catch (id exception) {}
465 : [self queueResult:task];
466 : }
467 :
468 : @end
|