]>
Commit | Line | Data |
---|---|---|
8a50f688 A |
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 | ||
3f0f0d49 A |
24 | #if OCTAGON |
25 | ||
8a50f688 | 26 | #import "keychain/ckks/CKKSResultOperation.h" |
3f0f0d49 | 27 | #import "keychain/ckks/NSOperationCategories.h" |
8a50f688 | 28 | #import "keychain/ckks/CKKSCondition.h" |
79b9da22 | 29 | #import "keychain/categories/NSError+UsefulConstructors.h" |
b54c578e | 30 | #import "keychain/ot/ObjCImprovements.h" |
8a50f688 A |
31 | #include <utilities/debugging.h> |
32 | ||
33 | @interface CKKSResultOperation() | |
34 | @property NSMutableArray<CKKSResultOperation*>* successDependencies; | |
35 | @property bool timeoutCanOccur; | |
36 | @property dispatch_queue_t timeoutQueue; | |
37 | @property void (^finishingBlock)(void); | |
38 | @end | |
39 | ||
40 | @implementation CKKSResultOperation | |
41 | - (instancetype)init { | |
42 | if(self = [super init]) { | |
b54c578e | 43 | WEAKIFY(self); |
8a50f688 A |
44 | _error = nil; |
45 | _successDependencies = [[NSMutableArray alloc] init]; | |
46 | _timeoutCanOccur = true; | |
7512f6be | 47 | _timeoutQueue = dispatch_queue_create("result-operation-timeout", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL); |
8a50f688 A |
48 | _completionHandlerDidRunCondition = [[CKKSCondition alloc] init]; |
49 | ||
8a50f688 | 50 | _finishingBlock = ^(void) { |
b54c578e A |
51 | STRONGIFY(self); |
52 | self.finishDate = [NSDate dateWithTimeIntervalSinceNow:0]; | |
8a50f688 A |
53 | }; |
54 | self.completionBlock = ^{}; // our _finishing block gets added in the method override | |
55 | } | |
56 | return self; | |
57 | } | |
58 | ||
ecaf5866 A |
59 | - (NSString*)operationStateString { |
60 | return ([self isFinished] ? [NSString stringWithFormat:@"finished %@", self.finishDate] : | |
61 | [self isCancelled] ? @"cancelled" : | |
62 | [self isExecuting] ? @"executing" : | |
63 | [self isReady] ? @"ready" : | |
64 | @"pending"); | |
65 | } | |
66 | ||
b54c578e | 67 | |
8a50f688 | 68 | - (NSString*)description { |
b54c578e | 69 | static __thread unsigned __descriptionRecursion = 0; |
ecaf5866 | 70 | NSString* state = [self operationStateString]; |
b54c578e | 71 | NSString *desc = NULL; |
8a50f688 | 72 | |
b54c578e A |
73 | __descriptionRecursion++; |
74 | if(__descriptionRecursion > 10) { | |
75 | desc = [NSString stringWithFormat: @"<%@: %@ recursion>", [self selfname], state]; | |
76 | } else if(self.error) { | |
77 | desc = [NSString stringWithFormat: @"<%@: %@ error:%@>", [self selfname], state, self.error]; | |
8a50f688 | 78 | } else { |
b54c578e | 79 | desc = [NSString stringWithFormat: @"<%@: %@%@>", [self selfname], state, [self pendingDependenciesString:@" dep:"]]; |
8a50f688 | 80 | } |
b54c578e A |
81 | __descriptionRecursion--; |
82 | return desc; | |
8a50f688 A |
83 | } |
84 | ||
85 | - (NSString*)debugDescription { | |
86 | return [self description]; | |
87 | } | |
88 | ||
89 | - (void)setCompletionBlock:(void (^)(void))completionBlock | |
90 | { | |
b54c578e | 91 | WEAKIFY(self); |
8a50f688 | 92 | [super setCompletionBlock:^(void) { |
b54c578e A |
93 | STRONGIFY(self); |
94 | if (!self) { | |
8a50f688 A |
95 | secerror("ckksresultoperation: completion handler called on deallocated operation instance"); |
96 | completionBlock(); // go ahead and still behave as things would if this method override were not here | |
97 | return; | |
98 | } | |
99 | ||
b54c578e | 100 | self.finishingBlock(); |
8a50f688 | 101 | completionBlock(); |
b54c578e | 102 | [self.completionHandlerDidRunCondition fulfill]; |
ecaf5866 | 103 | |
b54c578e A |
104 | for (NSOperation *op in self.dependencies) { |
105 | [self removeDependency:op]; | |
ecaf5866 | 106 | } |
8a50f688 A |
107 | }]; |
108 | } | |
109 | ||
110 | - (void)start { | |
111 | if(![self allDependentsSuccessful]) { | |
112 | secdebug("ckksresultoperation", "Not running due to some failed dependent: %@", self.error); | |
113 | [self cancel]; | |
114 | } else { | |
115 | [self invalidateTimeout]; | |
116 | ||
117 | } | |
118 | ||
119 | [super start]; | |
120 | } | |
121 | ||
122 | - (void)invalidateTimeout { | |
123 | dispatch_sync(self.timeoutQueue, ^{ | |
124 | if(![self isCancelled]) { | |
125 | self.timeoutCanOccur = false; | |
126 | }; | |
127 | }); | |
128 | } | |
129 | ||
ecaf5866 A |
130 | - (NSError* _Nullable)dependenciesDescriptionError { |
131 | NSError* underlyingReason = nil; | |
132 | NSArray* dependencies = [self.dependencies copy]; | |
133 | dependencies = [dependencies objectsAtIndexes: [dependencies indexesOfObjectsPassingTest: ^BOOL (id obj, | |
134 | NSUInteger idx, | |
135 | BOOL* stop) { | |
136 | return [obj isFinished] ? NO : YES; | |
137 | }]]; | |
138 | ||
139 | for(NSOperation* dependency in dependencies) { | |
140 | if([dependency isKindOfClass:[CKKSResultOperation class]]) { | |
141 | CKKSResultOperation* ro = (CKKSResultOperation*)dependency; | |
142 | underlyingReason = [ro descriptionError] ?: underlyingReason; | |
143 | } | |
144 | } | |
145 | ||
146 | return underlyingReason; | |
147 | } | |
148 | ||
149 | // Returns, for this CKKSResultOperation, an error describing this operation or its dependents. | |
150 | // Used mainly by other CKKSResultOperations who time out waiting for this operation to start/complete. | |
151 | - (NSError* _Nullable)descriptionError { | |
152 | if(self.descriptionErrorCode != 0) { | |
153 | return [NSError errorWithDomain:CKKSResultDescriptionErrorDomain | |
154 | code:self.descriptionErrorCode | |
155 | userInfo:nil]; | |
156 | } else { | |
157 | return [self dependenciesDescriptionError]; | |
158 | } | |
159 | } | |
160 | ||
161 | - (NSError*)_onqueueTimeoutError { | |
162 | // Find if any of our dependencies are CKKSResultOperations with a custom reason for existing | |
163 | ||
164 | NSError* underlyingReason = [self descriptionError]; | |
165 | ||
166 | NSError* error = [NSError errorWithDomain:CKKSResultErrorDomain | |
167 | code:CKKSResultTimedOut | |
168 | description:[NSString stringWithFormat:@"Operation(%@) timed out waiting to start for [%@]", | |
169 | [self selfname], | |
170 | [self pendingDependenciesString:@""]] | |
171 | underlying:underlyingReason]; | |
172 | return error; | |
173 | } | |
174 | ||
8a50f688 | 175 | - (instancetype)timeout:(dispatch_time_t)timeout { |
b54c578e | 176 | WEAKIFY(self); |
8a50f688 | 177 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeout), self.timeoutQueue, ^{ |
b54c578e A |
178 | STRONGIFY(self); |
179 | if(self.timeoutCanOccur) { | |
180 | self.error = [self _onqueueTimeoutError]; | |
181 | self.timeoutCanOccur = false; | |
182 | [self cancel]; | |
8a50f688 A |
183 | } |
184 | }); | |
185 | ||
186 | return self; | |
187 | } | |
188 | ||
189 | - (void)addSuccessDependency:(CKKSResultOperation *)operation { | |
190 | [self addNullableSuccessDependency:operation]; | |
191 | } | |
192 | ||
193 | - (void)addNullableSuccessDependency:(CKKSResultOperation *)operation { | |
194 | if(!operation) { | |
195 | return; | |
196 | } | |
197 | @synchronized(self) { | |
198 | [self.successDependencies addObject: operation]; | |
199 | [self addDependency: operation]; | |
200 | } | |
201 | } | |
202 | ||
203 | - (bool)allDependentsSuccessful { | |
204 | return [self allSuccessful: self.successDependencies]; | |
205 | } | |
206 | ||
207 | - (bool)allSuccessful: (NSArray<CKKSResultOperation*>*) operations { | |
208 | @synchronized(self) { | |
209 | bool result = false; | |
210 | ||
211 | bool finished = true; // all dependents must be finished | |
212 | bool cancelled = false; // no dependents can be cancelled | |
213 | bool failed = false; // no dependents can have failed | |
3f0f0d49 | 214 | NSMutableArray<NSOperation*>* cancelledSuboperations = [NSMutableArray array]; |
8a50f688 A |
215 | |
216 | for(CKKSResultOperation* op in operations) { | |
217 | finished &= !!([op isFinished]); | |
218 | cancelled |= !!([op isCancelled]); | |
219 | failed |= (op.error != nil); | |
220 | ||
3f0f0d49 A |
221 | if([op isCancelled]) { |
222 | [cancelledSuboperations addObject:op]; | |
223 | } | |
224 | ||
8a50f688 A |
225 | // TODO: combine suberrors |
226 | if(op.error != nil) { | |
227 | if([op.error.domain isEqual: CKKSResultErrorDomain] && op.error.code == CKKSResultSubresultError) { | |
228 | // Already a subresult, just copy it on in | |
229 | self.error = op.error; | |
230 | } else { | |
ecaf5866 A |
231 | self.error = [NSError errorWithDomain:CKKSResultErrorDomain |
232 | code:CKKSResultSubresultError | |
233 | description:@"Success-dependent operation failed" | |
234 | underlying:op.error]; | |
8a50f688 A |
235 | } |
236 | } | |
237 | } | |
238 | ||
239 | result = finished && !( cancelled || failed ); | |
240 | ||
241 | if(!result && self.error == nil) { | |
3f0f0d49 | 242 | self.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultSubresultCancelled userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Operation (%@) cancelled", cancelledSuboperations]}]; |
8a50f688 A |
243 | } |
244 | return result; | |
245 | } | |
246 | } | |
247 | ||
248 | + (CKKSResultOperation*)operationWithBlock:(void (^)(void))block { | |
b54c578e | 249 | CKKSResultOperation* op = [[self alloc] init]; |
8a50f688 A |
250 | [op addExecutionBlock: block]; |
251 | return op; | |
252 | } | |
253 | ||
b54c578e A |
254 | + (instancetype)named:(NSString*)name withBlock:(void(^)(void)) block { |
255 | CKKSResultOperation* blockOp = [self operationWithBlock: block]; | |
8a50f688 A |
256 | blockOp.name = name; |
257 | return blockOp; | |
258 | } | |
3f0f0d49 A |
259 | |
260 | + (instancetype)named:(NSString*)name withBlockTakingSelf:(void(^)(CKKSResultOperation* op))block | |
261 | { | |
b54c578e | 262 | CKKSResultOperation* op = [[self alloc] init]; |
3f0f0d49 A |
263 | __weak __typeof(op) weakOp = op; |
264 | [op addExecutionBlock:^{ | |
265 | __strong __typeof(op) strongOp = weakOp; | |
266 | block(strongOp); | |
267 | }]; | |
268 | op.name = name; | |
269 | return op; | |
270 | } | |
8a50f688 | 271 | @end |
3f0f0d49 A |
272 | |
273 | #endif // OCTAGON |