]>
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" |
d64be36e | 31 | #import "keychain/ckks/CKKS.h" |
8a50f688 A |
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) { | |
d64be36e | 95 | ckkserror_global("resultoperation", "completion handler called on deallocated operation instance"); |
8a50f688 A |
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 { | |
d64be36e A |
152 | static __thread unsigned __descriptionRecursion = 0; |
153 | ||
154 | NSError* result = nil; | |
155 | ||
156 | __descriptionRecursion += 1; | |
157 | ||
ecaf5866 | 158 | if(self.descriptionErrorCode != 0) { |
d64be36e A |
159 | result = [NSError errorWithDomain:CKKSResultDescriptionErrorDomain |
160 | code:self.descriptionErrorCode | |
161 | userInfo:nil]; | |
162 | } else if(__descriptionRecursion > 10) { | |
163 | result = [NSError errorWithDomain:CKKSResultDescriptionErrorDomain | |
164 | code:-1 | |
165 | description:@"Excess recursion"]; | |
ecaf5866 | 166 | } else { |
d64be36e | 167 | result = [self dependenciesDescriptionError]; |
ecaf5866 | 168 | } |
d64be36e A |
169 | |
170 | __descriptionRecursion -= 1; | |
171 | ||
172 | return result; | |
ecaf5866 A |
173 | } |
174 | ||
175 | - (NSError*)_onqueueTimeoutError { | |
d64be36e | 176 | dispatch_assert_queue(self.timeoutQueue); |
ecaf5866 A |
177 | // Find if any of our dependencies are CKKSResultOperations with a custom reason for existing |
178 | ||
179 | NSError* underlyingReason = [self descriptionError]; | |
180 | ||
181 | NSError* error = [NSError errorWithDomain:CKKSResultErrorDomain | |
182 | code:CKKSResultTimedOut | |
183 | description:[NSString stringWithFormat:@"Operation(%@) timed out waiting to start for [%@]", | |
184 | [self selfname], | |
185 | [self pendingDependenciesString:@""]] | |
186 | underlying:underlyingReason]; | |
187 | return error; | |
188 | } | |
189 | ||
8a50f688 | 190 | - (instancetype)timeout:(dispatch_time_t)timeout { |
b54c578e | 191 | WEAKIFY(self); |
8a50f688 | 192 | dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeout), self.timeoutQueue, ^{ |
b54c578e A |
193 | STRONGIFY(self); |
194 | if(self.timeoutCanOccur) { | |
195 | self.error = [self _onqueueTimeoutError]; | |
196 | self.timeoutCanOccur = false; | |
197 | [self cancel]; | |
8a50f688 A |
198 | } |
199 | }); | |
200 | ||
201 | return self; | |
202 | } | |
203 | ||
204 | - (void)addSuccessDependency:(CKKSResultOperation *)operation { | |
205 | [self addNullableSuccessDependency:operation]; | |
206 | } | |
207 | ||
208 | - (void)addNullableSuccessDependency:(CKKSResultOperation *)operation { | |
209 | if(!operation) { | |
210 | return; | |
211 | } | |
212 | @synchronized(self) { | |
213 | [self.successDependencies addObject: operation]; | |
214 | [self addDependency: operation]; | |
215 | } | |
216 | } | |
217 | ||
218 | - (bool)allDependentsSuccessful { | |
219 | return [self allSuccessful: self.successDependencies]; | |
220 | } | |
221 | ||
222 | - (bool)allSuccessful: (NSArray<CKKSResultOperation*>*) operations { | |
223 | @synchronized(self) { | |
224 | bool result = false; | |
225 | ||
226 | bool finished = true; // all dependents must be finished | |
227 | bool cancelled = false; // no dependents can be cancelled | |
228 | bool failed = false; // no dependents can have failed | |
3f0f0d49 | 229 | NSMutableArray<NSOperation*>* cancelledSuboperations = [NSMutableArray array]; |
8a50f688 A |
230 | |
231 | for(CKKSResultOperation* op in operations) { | |
232 | finished &= !!([op isFinished]); | |
233 | cancelled |= !!([op isCancelled]); | |
234 | failed |= (op.error != nil); | |
235 | ||
3f0f0d49 A |
236 | if([op isCancelled]) { |
237 | [cancelledSuboperations addObject:op]; | |
238 | } | |
239 | ||
8a50f688 A |
240 | // TODO: combine suberrors |
241 | if(op.error != nil) { | |
242 | if([op.error.domain isEqual: CKKSResultErrorDomain] && op.error.code == CKKSResultSubresultError) { | |
243 | // Already a subresult, just copy it on in | |
244 | self.error = op.error; | |
245 | } else { | |
ecaf5866 A |
246 | self.error = [NSError errorWithDomain:CKKSResultErrorDomain |
247 | code:CKKSResultSubresultError | |
248 | description:@"Success-dependent operation failed" | |
249 | underlying:op.error]; | |
8a50f688 A |
250 | } |
251 | } | |
252 | } | |
253 | ||
254 | result = finished && !( cancelled || failed ); | |
255 | ||
256 | if(!result && self.error == nil) { | |
3f0f0d49 | 257 | self.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultSubresultCancelled userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Operation (%@) cancelled", cancelledSuboperations]}]; |
8a50f688 A |
258 | } |
259 | return result; | |
260 | } | |
261 | } | |
262 | ||
263 | + (CKKSResultOperation*)operationWithBlock:(void (^)(void))block { | |
b54c578e | 264 | CKKSResultOperation* op = [[self alloc] init]; |
8a50f688 A |
265 | [op addExecutionBlock: block]; |
266 | return op; | |
267 | } | |
268 | ||
b54c578e A |
269 | + (instancetype)named:(NSString*)name withBlock:(void(^)(void)) block { |
270 | CKKSResultOperation* blockOp = [self operationWithBlock: block]; | |
8a50f688 A |
271 | blockOp.name = name; |
272 | return blockOp; | |
273 | } | |
3f0f0d49 A |
274 | |
275 | + (instancetype)named:(NSString*)name withBlockTakingSelf:(void(^)(CKKSResultOperation* op))block | |
276 | { | |
b54c578e | 277 | CKKSResultOperation* op = [[self alloc] init]; |
3f0f0d49 A |
278 | __weak __typeof(op) weakOp = op; |
279 | [op addExecutionBlock:^{ | |
280 | __strong __typeof(op) strongOp = weakOp; | |
281 | block(strongOp); | |
282 | }]; | |
283 | op.name = name; | |
284 | return op; | |
285 | } | |
805875f8 | 286 | |
8a50f688 | 287 | @end |
3f0f0d49 A |
288 | |
289 | #endif // OCTAGON |