]> git.saurik.com Git - apple/security.git/blob - keychain/ckks/CKKSGroupOperation.m
Security-59754.80.3.tar.gz
[apple/security.git] / keychain / ckks / CKKSGroupOperation.m
1 /*
2 * Copyright (c) 2016 Apple Inc. All Rights Reserved.
3 *
4 * @APPLE_LICENSE_HEADER_START@
5 *
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
11 * file.
12 *
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.
20 *
21 * @APPLE_LICENSE_HEADER_END@
22 */
23
24 #if OCTAGON
25
26 #import "CKKSGroupOperation.h"
27 #import "keychain/ot/ObjCImprovements.h"
28 #import "keychain/ckks/CKKS.h"
29
30 @interface CKKSGroupOperation()
31 @property bool fillInError;
32 @property NSBlockOperation* startOperation;
33 @property NSBlockOperation* finishOperation;
34 @property dispatch_queue_t queue;
35
36 @property NSMutableArray<CKKSResultOperation*>* internalSuccesses;
37 @end
38
39 @implementation CKKSGroupOperation
40
41 - (instancetype)init {
42 if(self = [super init]) {
43 WEAKIFY(self);
44
45 _fillInError = true;
46
47 _operationQueue = [[NSOperationQueue alloc] init];
48 _internalSuccesses = [[NSMutableArray alloc] init];
49
50 _queue = dispatch_queue_create("CKKSGroupOperationDispatchQueue", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
51
52 // At start, we'll call this method (for subclasses)
53 _startOperation = [NSBlockOperation blockOperationWithBlock:^{
54 STRONGIFY(self);
55 if(!self) {
56 ckkserror_global("ckks", "received callback for released object");
57 return;
58 }
59
60 if(![self allDependentsSuccessful]) {
61 ckksinfo_global("ckksgroup", "Not running due to some failed dependent: %@", self.error);
62 [self cancel];
63 return;
64 }
65
66 [self groupStart];
67 }];
68 [self.startOperation removeDependenciesUponCompletion];
69
70 // The finish operation will 'finish' us
71 _finishOperation = [NSBlockOperation blockOperationWithBlock:^{
72 STRONGIFY(self);
73 if(!self) {
74 ckkserror_global("ckks", "received callback for released object");
75 return;
76 }
77
78 [self completeOperation];
79 }];
80 [self.finishOperation removeDependenciesUponCompletion];
81
82 [self.finishOperation addDependency: self.startOperation];
83 [self.operationQueue addOperation: self.finishOperation];
84
85 self.startOperation.name = @"group-start";
86 self.finishOperation.name = @"group-finish";
87
88 executing = NO;
89 finished = NO;
90 }
91 return self;
92 }
93
94 - (void)dealloc {
95 // If the GroupOperation is dealloced before starting, all of its downstream operations form a retain loop.
96
97 if([self isPending]) {
98 [self.operationQueue cancelAllOperations];
99 [self.startOperation cancel];
100 [super cancel];
101 }
102 }
103
104 // We are pending if our start operation is pending but we are also not cancelled yet
105 - (BOOL)isPending {
106 return [self.startOperation isPending] && ![self isCancelled];
107 }
108
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];
114 }
115
116 - (NSString*)description {
117
118 static __thread unsigned __descriptionRecursion = 0;
119 NSString* state = [self operationStateString];
120 NSString *desc = NULL;
121
122 __descriptionRecursion++;
123
124 if(__descriptionRecursion > 10) {
125 desc = [NSString stringWithFormat: @"<%@: %@ recursion>", [self selfname], state];
126
127 } else if(self.isFinished) {
128 if(self.error) {
129 desc = [NSString stringWithFormat: @"<%@: %@ %@ - %@>", [self selfname],
130 state,
131 self.finishDate,
132 self.error];
133 } else {
134 desc = [NSString stringWithFormat: @"<%@: %@ %@>", [self selfname],
135 state,
136 self.finishDate];
137 }
138 } else {
139
140 NSString* opsString = nil;
141
142 if (self.operationQueue.operationCount + self.finishOperation.dependencies.count > 20) {
143 opsString = @"Potentially more than 20 operations";
144 } else {
145 NSMutableArray* ops = [self.operationQueue.operations mutableCopy];
146
147 [ops removeObject: self.finishOperation];
148
149 // Any extra dependencies from the finish operation should be considered part of this group
150 for(NSOperation* finishDep in self.finishOperation.dependencies) {
151 if (ops.count > 20) {
152 opsString = @"Potentially more than 20 operations";
153 break;
154 }
155 if(finishDep != self.startOperation && (NSNotFound == [ops indexOfObject: finishDep])) {
156 [ops addObject: finishDep];
157 }
158 }
159 if (opsString == nil) {
160 opsString = [ops componentsJoinedByString:@", "];
161 }
162 }
163
164 if(self.error) {
165 desc = [NSString stringWithFormat: @"<%@: %@ [%@] error:%@>", [self selfname], state, opsString, self.error];
166 } else {
167 desc = [NSString stringWithFormat: @"<%@: %@ [%@]%@>", [self selfname], state, opsString, [self pendingDependenciesString:@" dep:"]];
168 }
169 }
170 __descriptionRecursion--;
171
172 return desc;
173 }
174
175 - (NSString*)debugDescription {
176 return [self description];
177 }
178
179 - (BOOL)isConcurrent {
180 return YES;
181 }
182
183 - (BOOL)isExecuting {
184 __block BOOL ret = FALSE;
185 dispatch_sync(self.queue, ^{
186 ret = self->executing;
187 });
188 return ret;
189 }
190
191 - (BOOL)isFinished {
192 __block BOOL ret = FALSE;
193 dispatch_sync(self.queue, ^{
194 ret = self->finished;
195 });
196 return ret;
197 }
198
199 - (void)start {
200 [self invalidateTimeout];
201
202 if([self isCancelled]) {
203 [self willChangeValueForKey:@"isFinished"];
204 dispatch_sync(self.queue, ^{
205 self->finished = YES;
206 });
207 [self didChangeValueForKey:@"isFinished"];
208 return;
209 }
210
211 [self.operationQueue addOperation: self.startOperation];
212
213 [self willChangeValueForKey:@"isExecuting"];
214 dispatch_sync(self.queue, ^{
215 self->executing = YES;
216 });
217 [self didChangeValueForKey:@"isExecuting"];
218 }
219
220 - (void)cancel {
221
222 // Block off the start operation
223 NSBlockOperation* block = [NSBlockOperation blockOperationWithBlock:^{}];
224 [self.startOperation addDependency: block];
225
226 [super cancel];
227
228 // Cancel all operations currently on the queue, except for the finish operation
229 NSArray<NSOperation*>* ops = [self.operationQueue.operations copy];
230 for(NSOperation* op in ops) {
231 if(![op isEqual: self.finishOperation]) {
232 [op cancel];
233 }
234 }
235
236 NSArray<NSOperation*>* finishDependencies = [self.finishOperation.dependencies copy];
237 for(NSOperation* finishDep in finishDependencies) {
238 if(!([ops containsObject: finishDep] || [finishDep isEqual:self.startOperation])) {
239 // This is finish dependency that we don't control (and isn't our start operation)
240 // Since we're cancelled, don't wait for it.
241 [self.finishOperation removeDependency: finishDep];
242 }
243 }
244
245 if([self.startOperation isPending]) {
246 // If we were cancelled before starting, don't fill in our error later; we'll probably just get subresult cancelled
247 self.fillInError = false;
248 }
249
250 // Now, we're in a position where either:
251 // 1. This operation hasn't been started, and is now 'cancelled'
252 // 2. This operation has beens started, and is now cancelled, and has delivered a 'cancel' message to all its suboperations,
253 // which may or may not comply
254 //
255 // In either case, this operation will complete its finish operation whenever it is 'started' and all of its cancelled suboperations finish.
256
257 [self.operationQueue addOperation: block];
258 }
259
260 - (void)completeOperation {
261 [self willChangeValueForKey:@"isFinished"];
262 [self willChangeValueForKey:@"isExecuting"];
263
264 dispatch_sync(self.queue, ^{
265 if(self.fillInError) {
266 // Run through all the failable operations in this group, and determine if we should be considered successful ourselves
267 [self allSuccessful: self.internalSuccesses];
268 }
269
270 self->executing = NO;
271 self->finished = YES;
272 });
273
274 [self didChangeValueForKey:@"isExecuting"];
275 [self didChangeValueForKey:@"isFinished"];
276 }
277
278 - (void)addDependency:(NSOperation *)op {
279 [super addDependency:op];
280 [self.startOperation addDependency: op];
281 }
282
283 - (void)groupStart {
284 // Do nothing. Subclasses can do things here.
285 }
286
287 - (void)runBeforeGroupFinished: (NSOperation*) suboperation {
288
289 // op must wait for this operation to start
290 [suboperation addDependency: self.startOperation];
291
292 [self dependOnBeforeGroupFinished: suboperation];
293 [self.operationQueue addOperation: suboperation];
294 }
295
296 - (void)dependOnBeforeGroupFinished: (NSOperation*) suboperation {
297 if(suboperation == nil) {
298 return;
299 }
300
301 if([self isCancelled]) {
302 // Cancelled operations can't add anything.
303 ckksnotice_global("ckksgroup", "Can't add operation dependency to cancelled group");
304 return;
305 }
306
307 // Make sure we wait for it.
308 [self.finishOperation addDependency: suboperation];
309 if([self.finishOperation isFinished]) {
310 @throw [NSException exceptionWithName:NSInternalInconsistencyException reason:[NSString stringWithFormat:@"Attempt to add operation(%@) to completed group(%@)", suboperation, self] userInfo:nil];
311 }
312
313 // And it waits for us.
314 [suboperation addDependency: self.startOperation];
315
316 // If this is a CKKSResultOperation, then its result impacts our result.
317 if([suboperation isKindOfClass: [CKKSResultOperation class]]) {
318 // don't use addSuccessDependency, because it's not a dependency for The Group Operation, but rather a suboperation
319 @synchronized(self) {
320 [self.internalSuccesses addObject: (CKKSResultOperation*) suboperation];
321 }
322 }
323 }
324
325 + (instancetype)operationWithBlock:(void (^)(void))block {
326 CKKSGroupOperation* op = [[self alloc] init];
327 NSBlockOperation* blockOp = [NSBlockOperation blockOperationWithBlock:block];
328 [op runBeforeGroupFinished:blockOp];
329 return op;
330 }
331
332 + (instancetype)named:(NSString*)name withBlock:(void(^)(void)) block {
333 CKKSGroupOperation* blockOp = [self operationWithBlock: block];
334 blockOp.name = name;
335 return blockOp;
336 }
337
338 + (instancetype)named:(NSString*)name withBlockTakingSelf:(void(^)(CKKSGroupOperation* strongOp))block
339 {
340 CKKSGroupOperation* op = [[self alloc] init];
341 __weak __typeof(op) weakOp = op;
342 [op runBeforeGroupFinished:[NSBlockOperation blockOperationWithBlock:^{
343 __strong __typeof(op) strongOp = weakOp;
344 block(strongOp);
345 }]];
346 op.name = name;
347 return op;
348 }
349
350 @end
351
352 #endif // OCTAGON
353