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@
26 #import "CKKSGroupOperation.h"
27 #import "keychain/ot/ObjCImprovements.h"
28 #include <utilities/debugging.h>
30 @interface CKKSGroupOperation()
31 @property bool fillInError;
32 @property NSBlockOperation* startOperation;
33 @property NSBlockOperation* finishOperation;
34 @property dispatch_queue_t queue;
36 @property NSMutableArray<CKKSResultOperation*>* internalSuccesses;
39 @implementation CKKSGroupOperation
41 - (instancetype)init {
42 if(self = [super init]) {
47 _operationQueue = [[NSOperationQueue alloc] init];
48 _internalSuccesses = [[NSMutableArray alloc] init];
50 _queue = dispatch_queue_create("CKKSGroupOperationDispatchQueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
52 // At start, we'll call this method (for subclasses)
53 _startOperation = [NSBlockOperation blockOperationWithBlock:^{
56 secerror("ckks: received callback for released object");
60 if(![self allDependentsSuccessful]) {
61 secdebug("ckksgroup", "Not running due to some failed dependent: %@", self.error);
68 [self.startOperation removeDependenciesUponCompletion];
70 // The finish operation will 'finish' us
71 _finishOperation = [NSBlockOperation blockOperationWithBlock:^{
74 secerror("ckks: received callback for released object");
78 [self completeOperation];
80 [self.finishOperation removeDependenciesUponCompletion];
82 [self.finishOperation addDependency: self.startOperation];
83 [self.operationQueue addOperation: self.finishOperation];
85 self.startOperation.name = @"group-start";
86 self.finishOperation.name = @"group-finish";
95 // If the GroupOperation is dealloced before starting, all of its downstream operations form a retain loop.
97 if([self isPending]) {
98 [self.operationQueue cancelAllOperations];
99 [self.startOperation cancel];
104 // We are pending if our start operation is pending but we are also not cancelled yet
106 return [self.startOperation isPending] && ![self isCancelled];
109 - (void)setName:(NSString*) name {
110 self.operationQueue.name = [NSString stringWithFormat: @"group-queue:%@", name];
111 self.startOperation.name = [NSString stringWithFormat: @"group-start:%@", name];
112 self.finishOperation.name = [NSString stringWithFormat: @"group-finish:%@", name];
113 [super setName: name];
116 - (NSString*)description {
117 if(self.isFinished) {
119 return [NSString stringWithFormat: @"<%@: %@ %@ - %@>", [self selfname],
120 [self operationStateString],
124 return [NSString stringWithFormat: @"<%@: %@ %@>", [self selfname],
125 [self operationStateString],
130 NSMutableArray* ops = [self.operationQueue.operations mutableCopy];
132 [ops removeObject: self.finishOperation];
134 // Any extra dependencies from the finish operation should be considered part of this group
135 for(NSOperation* finishDep in self.finishOperation.dependencies) {
136 if(finishDep != self.startOperation && (NSNotFound == [ops indexOfObject: finishDep])) {
137 [ops addObject: finishDep];
141 NSString* opsString = [ops componentsJoinedByString:@", "];
144 return [NSString stringWithFormat: @"<%@: %@ [%@] error:%@>", [self selfname], [self operationStateString], opsString, self.error];
146 return [NSString stringWithFormat: @"<%@: %@ [%@]%@>", [self selfname], [self operationStateString], opsString, [self pendingDependenciesString:@" dep:"]];
150 - (NSString*)debugDescription {
151 return [self description];
154 - (BOOL)isConcurrent {
158 - (BOOL)isExecuting {
159 __block BOOL ret = FALSE;
160 dispatch_sync(self.queue, ^{
161 ret = self->executing;
167 __block BOOL ret = FALSE;
168 dispatch_sync(self.queue, ^{
169 ret = self->finished;
175 [self invalidateTimeout];
177 if([self isCancelled]) {
178 [self willChangeValueForKey:@"isFinished"];
179 dispatch_sync(self.queue, ^{
180 self->finished = YES;
182 [self didChangeValueForKey:@"isFinished"];
186 [self.operationQueue addOperation: self.startOperation];
188 [self willChangeValueForKey:@"isExecuting"];
189 dispatch_sync(self.queue, ^{
190 self->executing = YES;
192 [self didChangeValueForKey:@"isExecuting"];
197 // Block off the start operation
198 NSBlockOperation* block = [NSBlockOperation blockOperationWithBlock:^{}];
199 [self.startOperation addDependency: block];
203 // Cancel all operations currently on the queue, except for the finish operation
204 NSArray<NSOperation*>* ops = [self.operationQueue.operations copy];
205 for(NSOperation* op in ops) {
206 if(![op isEqual: self.finishOperation]) {
211 NSArray<NSOperation*>* finishDependencies = [self.finishOperation.dependencies copy];
212 for(NSOperation* finishDep in finishDependencies) {
213 if(!([ops containsObject: finishDep] || [finishDep isEqual:self.startOperation])) {
214 // This is finish dependency that we don't control (and isn't our start operation)
215 // Since we're cancelled, don't wait for it.
216 [self.finishOperation removeDependency: finishDep];
220 if([self.startOperation isPending]) {
221 // If we were cancelled before starting, don't fill in our error later; we'll probably just get subresult cancelled
222 self.fillInError = false;
225 // Now, we're in a position where either:
226 // 1. This operation hasn't been started, and is now 'cancelled'
227 // 2. This operation has beens started, and is now cancelled, and has delivered a 'cancel' message to all its suboperations,
228 // which may or may not comply
230 // In either case, this operation will complete its finish operation whenever it is 'started' and all of its cancelled suboperations finish.
232 [self.operationQueue addOperation: block];
235 - (void)completeOperation {
236 [self willChangeValueForKey:@"isFinished"];
237 [self willChangeValueForKey:@"isExecuting"];
239 dispatch_sync(self.queue, ^{
240 if(self.fillInError) {
241 // Run through all the failable operations in this group, and determine if we should be considered successful ourselves
242 [self allSuccessful: self.internalSuccesses];
245 self->executing = NO;
246 self->finished = YES;
249 [self didChangeValueForKey:@"isExecuting"];
250 [self didChangeValueForKey:@"isFinished"];
253 - (void)addDependency:(NSOperation *)op {
254 [super addDependency:op];
255 [self.startOperation addDependency: op];
259 // Do nothing. Subclasses can do things here.
262 - (void)runBeforeGroupFinished: (NSOperation*) suboperation {
264 // op must wait for this operation to start
265 [suboperation addDependency: self.startOperation];
267 [self dependOnBeforeGroupFinished: suboperation];
268 [self.operationQueue addOperation: suboperation];
271 - (void)dependOnBeforeGroupFinished: (NSOperation*) suboperation {
272 if(suboperation == nil) {
276 if([self isCancelled]) {
277 // Cancelled operations can't add anything.
278 secnotice("ckksgroup", "Can't add operation dependency to cancelled group");
282 // Make sure we wait for it.
283 [self.finishOperation addDependency: suboperation];
284 if([self.finishOperation isFinished]) {
285 @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"Attempt to add operation(%@) to completed group(%@)", suboperation, self] userInfo:nil];
288 // And it waits for us.
289 [suboperation addDependency: self.startOperation];
291 // If this is a CKKSResultOperation, then its result impacts our result.
292 if([suboperation isKindOfClass: [CKKSResultOperation class]]) {
293 // don't use addSuccessDependency, because it's not a dependency for The Group Operation, but rather a suboperation
294 @synchronized(self) {
295 [self.internalSuccesses addObject: (CKKSResultOperation*) suboperation];
300 + (instancetype)operationWithBlock:(void (^)(void))block {
301 CKKSGroupOperation* op = [[self alloc] init];
302 NSBlockOperation* blockOp = [NSBlockOperation blockOperationWithBlock:block];
303 [op runBeforeGroupFinished:blockOp];
307 + (instancetype)named:(NSString*)name withBlock:(void(^)(void)) block {
308 CKKSGroupOperation* blockOp = [self operationWithBlock: block];
313 + (instancetype)named:(NSString*)name withBlockTakingSelf:(void(^)(CKKSGroupOperation* strongOp))block
315 CKKSGroupOperation* op = [[self alloc] init];
316 __weak __typeof(op) weakOp = op;
317 [op runBeforeGroupFinished:[NSBlockOperation blockOperationWithBlock:^{
318 __strong __typeof(op) strongOp = weakOp;