2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
4 * @APPLE_LICENSE_HEADER_START@
6 * This file contains Original Code and/or Modifications of Original Code
7 * as defined in and that are subject to the Apple Public Source License
8 * Version 2.0 (the 'License'). You may not use this file except in
9 * compliance with the License. Please obtain a copy of the License at
10 * http://www.opensource.apple.com/apsl/ and read it before using this
13 * The Original Code and all software distributed under the License are
14 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
15 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
16 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
17 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
18 * Please see the License for the specific language governing rights and
19 * limitations under the License.
21 * @APPLE_LICENSE_HEADER_END@
24 #import "CKKSGroupOperation.h"
25 #import "CKKSCondition.h"
26 #include <utilities/debugging.h>
28 @implementation NSOperation (CKKSUsefulPrintingOperation)
29 - (NSString*)selfname {
31 return [NSString stringWithFormat: @"%@(%@)", NSStringFromClass([self class]), self.name];
33 return NSStringFromClass([self class]);
37 -(void)linearDependencies: (NSHashTable*) collection {
38 @synchronized(collection) {
39 for(NSOperation* existingop in collection) {
40 if(existingop == self) {
41 // don't depend on yourself
44 [self addDependency: existingop];
46 [collection addObject:self];
50 -(void)linearDependenciesWithSelfFirst: (NSHashTable*) collection {
51 @synchronized(collection) {
52 for(NSOperation* existingop in collection) {
53 if(existingop == self) {
54 // don't depend on yourself
58 if([existingop isPending]) {
59 [existingop addDependency: self];
60 if([existingop isPending]) {
61 // Good, we're ahead of this one.
63 // It started before we told it to wait on us. Reverse the dependency.
64 [existingop removeDependency: self];
65 [self addDependency:existingop];
68 // Not a pending op? We depend on it.
69 [self addDependency: existingop];
72 [collection addObject:self];
76 -(NSString*)pendingDependenciesString:(NSString*)prefix {
77 NSArray* dependencies = [self.dependencies copy];
78 dependencies = [dependencies objectsAtIndexes: [dependencies indexesOfObjectsPassingTest: ^BOOL (id obj,
81 return [obj isPending] ? YES : NO;
84 if(dependencies.count == 0u) {
88 return [NSString stringWithFormat: @"%@%@", prefix, [dependencies componentsJoinedByString: @", "]];
91 - (NSString*)description {
92 NSString* state = ([self isFinished] ? @"finished" :
93 [self isCancelled] ? @"cancelled" :
94 [self isExecuting] ? @"executing" :
95 [self isReady] ? @"ready" :
98 return [NSString stringWithFormat: @"<%@: %@%@>", [self selfname], state, [self pendingDependenciesString: @" dep:"]];
100 - (NSString*)debugDescription {
101 NSString* state = ([self isFinished] ? @"finished" :
102 [self isCancelled] ? @"cancelled" :
103 [self isExecuting] ? @"executing" :
104 [self isReady] ? @"ready" :
107 return [NSString stringWithFormat: @"<%@ (%p): %@%@>", [self selfname], self, state, [self pendingDependenciesString: @" dep:"]];
111 return (!([self isExecuting] || [self isFinished])) ? YES : NO;
114 - (void)addNullableDependency: (NSOperation*) op {
116 [self addDependency:op];
121 @implementation NSBlockOperation (CKKSUsefulConstructorOperation)
122 +(instancetype)named: (NSString*)name withBlock: (void(^)(void)) block {
123 // How many blocks could a block block if a block could block blocks?
124 NSBlockOperation* blockOp = [NSBlockOperation blockOperationWithBlock: block];
131 @interface CKKSResultOperation()
132 @property NSMutableArray<CKKSResultOperation*>* successDependencies;
133 @property bool timeoutCanOccur;
134 @property dispatch_queue_t timeoutQueue;
135 @property void (^finishingBlock)(void);
138 @implementation CKKSResultOperation
139 - (instancetype)init {
140 if(self = [super init]) {
142 _successDependencies = [[NSMutableArray alloc] init];
143 _timeoutCanOccur = true;
144 _timeoutQueue = dispatch_queue_create("result-operation-timeout", DISPATCH_QUEUE_SERIAL);
145 _completionHandlerDidRunCondition = [[CKKSCondition alloc] init];
147 __weak __typeof(self) weakSelf = self;
148 _finishingBlock = ^(void) {
149 weakSelf.finishDate = [NSDate dateWithTimeIntervalSinceNow:0];
151 self.completionBlock = ^{}; // our _finishing block gets added in the method override
156 - (NSString*)description {
157 NSString* state = ([self isFinished] ? [NSString stringWithFormat:@"finished %@", self.finishDate] :
158 [self isCancelled] ? @"cancelled" :
159 [self isExecuting] ? @"executing" :
160 [self isReady] ? @"ready" :
164 return [NSString stringWithFormat: @"<%@: %@ error:%@>", [self selfname], state, self.error];
166 return [NSString stringWithFormat: @"<%@: %@%@>", [self selfname], state, [self pendingDependenciesString:@" dep:"]];
170 - (NSString*)debugDescription {
171 return [self description];
174 - (void)setCompletionBlock:(void (^)(void))completionBlock
176 __weak __typeof(self) weakSelf = self;
177 [super setCompletionBlock:^(void) {
178 __strong __typeof(self) strongSelf = weakSelf;
180 secerror("ckksresultoperation: completion handler called on deallocated operation instance");
181 completionBlock(); // go ahead and still behave as things would if this method override were not here
185 strongSelf.finishingBlock();
187 [strongSelf.completionHandlerDidRunCondition fulfill];
192 if(![self allDependentsSuccessful]) {
193 secdebug("ckksresultoperation", "Not running due to some failed dependent: %@", self.error);
196 dispatch_sync(self.timeoutQueue, ^{
197 if(![self isCancelled]) {
198 self.timeoutCanOccur = false;
206 - (instancetype)timeout:(dispatch_time_t)timeout {
207 __weak __typeof(self) weakSelf = self;
208 dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeout), self.timeoutQueue, ^{
209 __strong __typeof(self) strongSelf = weakSelf;
210 if(strongSelf.timeoutCanOccur) {
211 strongSelf.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultTimedOut userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Operation timed out waiting to start for [%@]", [self pendingDependenciesString:@""]]}];
212 strongSelf.timeoutCanOccur = false;
220 - (void)addSuccessDependency: (CKKSResultOperation*) operation {
224 @synchronized(self) {
225 [self.successDependencies addObject: operation];
226 [self addDependency: operation];
230 - (bool)allDependentsSuccessful {
231 return [self allSuccessful: self.successDependencies];
234 - (bool)allSuccessful: (NSArray<CKKSResultOperation*>*) operations {
235 @synchronized(self) {
238 bool finished = true; // all dependents must be finished
239 bool cancelled = false; // no dependents can be cancelled
240 bool failed = false; // no dependents can have failed
242 for(CKKSResultOperation* op in operations) {
243 finished &= !!([op isFinished]);
244 cancelled |= !!([op isCancelled]);
245 failed |= (op.error != nil);
247 // TODO: combine suberrors
248 if(op.error != nil) {
249 if([op.error.domain isEqual: CKKSResultErrorDomain] && op.error.code == CKKSResultSubresultError) {
250 // Already a subresult, just copy it on in
251 self.error = op.error;
253 self.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultSubresultError userInfo:@{ NSUnderlyingErrorKey: op.error}];
258 result = finished && !( cancelled || failed );
260 if(!result && self.error == nil) {
261 self.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultSubresultCancelled userInfo:nil];
267 + (CKKSResultOperation*)operationWithBlock:(void (^)(void))block {
268 CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
269 [op addExecutionBlock: block];
273 +(instancetype)named:(NSString*)name withBlock:(void(^)(void)) block {
274 CKKSResultOperation* blockOp = [CKKSResultOperation operationWithBlock: block];
281 @interface CKKSGroupOperation()
282 @property NSBlockOperation* startOperation;
283 @property NSBlockOperation* finishOperation;
285 @property NSMutableArray<CKKSResultOperation*>* internalSuccesses;
289 @implementation CKKSGroupOperation
291 - (instancetype)init {
292 if(self = [super init]) {
293 __weak __typeof(self) weakSelf = self;
295 _operationQueue = [[NSOperationQueue alloc] init];
296 _internalSuccesses = [[NSMutableArray alloc] init];
298 // At start, we'll call this method (for subclasses)
299 _startOperation = [NSBlockOperation blockOperationWithBlock:^{
300 __strong __typeof(weakSelf) strongSelf = weakSelf;
302 secerror("ckks: received callback for released object");
306 if(![strongSelf allDependentsSuccessful]) {
307 secdebug("ckksgroup", "Not running due to some failed dependent: %@", strongSelf.error);
312 [strongSelf groupStart];
315 // The finish operation will 'finish' us
316 _finishOperation = [NSBlockOperation blockOperationWithBlock:^{
317 __strong __typeof(weakSelf) strongSelf = weakSelf;
319 secerror("ckks: received callback for released object");
323 [strongSelf completeOperation];
326 [self.finishOperation addDependency: self.startOperation];
327 [self.operationQueue addOperation: self.finishOperation];
329 self.startOperation.name = @"group-start";
330 self.finishOperation.name = @"group-finish";
339 // If the GroupOperation is dealloced before starting, all of its downstream operations form a retain loop.
341 if([self isPending]) {
342 [self.operationQueue cancelAllOperations];
343 [self.startOperation cancel];
349 return [self.startOperation isPending];
352 - (void)setName:(NSString*) name {
353 self.operationQueue.name = [NSString stringWithFormat: @"group-queue:%@", name];
354 self.startOperation.name = [NSString stringWithFormat: @"group-start:%@", name];
355 self.finishOperation.name = [NSString stringWithFormat: @"group-finish:%@", name];
356 [super setName: name];
359 - (NSString*)description {
360 if(self.isFinished) {
362 return [NSString stringWithFormat: @"<%@: finished %@ - %@>", [self selfname], self.finishDate, self.error];
364 return [NSString stringWithFormat: @"<%@: finished %@>", [self selfname], self.finishDate];
368 NSMutableArray* ops = [self.operationQueue.operations mutableCopy];
370 [ops removeObject: self.finishOperation];
372 // Any extra dependencies from the finish operation should be considered part of this group
373 for(NSOperation* finishDep in self.finishOperation.dependencies) {
374 if(finishDep != self.startOperation && (NSNotFound == [ops indexOfObject: finishDep])) {
375 [ops addObject: finishDep];
379 NSString* opsString = [ops componentsJoinedByString:@", "];
382 return [NSString stringWithFormat: @"<%@: [%@] error:%@>", [self selfname], opsString, self.error];
384 return [NSString stringWithFormat: @"<%@: [%@]%@>", [self selfname], opsString, [self pendingDependenciesString:@" dep:"]];
388 - (NSString*)debugDescription {
389 return [self description];
392 - (BOOL)isConcurrent {
396 - (BOOL)isExecuting {
397 return self->executing;
401 return self->finished;
405 if([self isCancelled]) {
406 [self willChangeValueForKey:@"isFinished"];
408 [self didChangeValueForKey:@"isFinished"];
412 [self.operationQueue addOperation: self.startOperation];
414 [self willChangeValueForKey:@"isExecuting"];
416 [self didChangeValueForKey:@"isExecuting"];
420 [self.operationQueue cancelAllOperations];
421 [self.startOperation cancel];
425 // Our finishoperation might not fire (as we cancelled it above), so let's help it out
426 [self completeOperation];
429 - (void)completeOperation {
430 [self willChangeValueForKey:@"isFinished"];
431 [self willChangeValueForKey:@"isExecuting"];
433 // Run through all the failable operations in this group, and determine if we should be considered successful ourselves
434 [self allSuccessful: self.internalSuccesses];
439 [self didChangeValueForKey:@"isExecuting"];
440 [self didChangeValueForKey:@"isFinished"];
443 - (void)addDependency:(NSOperation *)op {
444 [super addDependency:op];
445 [self.startOperation addDependency: op];
449 // Do nothing. Subclasses can do things here.
452 - (void)runBeforeGroupFinished: (NSOperation*) suboperation {
453 if([self isCancelled]) {
454 // Cancelled operations can't add anything.
455 secnotice("ckksgroup", "Not adding operation to cancelled group");
459 // op must wait for this operation to start
460 [suboperation addDependency: self.startOperation];
462 [self dependOnBeforeGroupFinished: suboperation];
463 [self.operationQueue addOperation: suboperation];
466 - (void)dependOnBeforeGroupFinished: (NSOperation*) suboperation {
467 if(suboperation == nil) {
471 if([self isCancelled]) {
472 // Cancelled operations can't add anything.
473 secnotice("ckksgroup", "Can't add operation dependency to cancelled group");
477 if([self.finishOperation isExecuting] || [self.finishOperation isFinished]) {
478 @throw @"Attempt to add operation to completed group";
481 // If this is a CKKSResultOperation, then its result impacts our result.
482 if([suboperation isKindOfClass: [CKKSResultOperation class]]) {
483 // don't use addSuccessDependency, because it's not a dependency for The Group Operation, but rather a suboperation
484 @synchronized(self) {
485 [self.internalSuccesses addObject: (CKKSResultOperation*) suboperation];
489 // Make sure it waits for us...
490 [suboperation addDependency: self.startOperation];
491 // and we wait for it.
492 [self.finishOperation addDependency: suboperation];