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