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