CKKSResultOperation.m [plain text]
/*
* Copyright (c) 2016 Apple Inc. All Rights Reserved.
*
* @APPLE_LICENSE_HEADER_START@
*
* This file contains Original Code and/or Modifications of Original Code
* as defined in and that are subject to the Apple Public Source License
* Version 2.0 (the 'License'). You may not use this file except in
* compliance with the License. Please obtain a copy of the License at
* http://www.opensource.apple.com/apsl/ and read it before using this
* file.
*
* The Original Code and all software distributed under the License are
* distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
* EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
* INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
* Please see the License for the specific language governing rights and
* limitations under the License.
*
* @APPLE_LICENSE_HEADER_END@
*/
#if OCTAGON
#import "keychain/ckks/CKKSResultOperation.h"
#import "keychain/ckks/NSOperationCategories.h"
#import "keychain/ckks/CKKSCondition.h"
#import "keychain/categories/NSError+UsefulConstructors.h"
#include <utilities/debugging.h>
@interface CKKSResultOperation()
@property NSMutableArray<CKKSResultOperation*>* successDependencies;
@property bool timeoutCanOccur;
@property dispatch_queue_t timeoutQueue;
@property void (^finishingBlock)(void);
@end
@implementation CKKSResultOperation
- (instancetype)init {
if(self = [super init]) {
_error = nil;
_successDependencies = [[NSMutableArray alloc] init];
_timeoutCanOccur = true;
_timeoutQueue = dispatch_queue_create("result-operation-timeout", DISPATCH_QUEUE_SERIAL_WITH_AUTORELEASE_POOL);
_completionHandlerDidRunCondition = [[CKKSCondition alloc] init];
__weak __typeof(self) weakSelf = self;
_finishingBlock = ^(void) {
weakSelf.finishDate = [NSDate dateWithTimeIntervalSinceNow:0];
};
self.completionBlock = ^{}; // our _finishing block gets added in the method override
}
return self;
}
- (NSString*)operationStateString {
return ([self isFinished] ? [NSString stringWithFormat:@"finished [self isCancelled] ? @"cancelled" :
[self isExecuting] ? @"executing" :
[self isReady] ? @"ready" :
@"pending");
}
- (NSString*)description {
NSString* state = [self operationStateString];
if(self.error) {
return [NSString stringWithFormat: @"< } else {
return [NSString stringWithFormat: @"< }
}
- (NSString*)debugDescription {
return [self description];
}
- (void)setCompletionBlock:(void (^)(void))completionBlock
{
__weak __typeof(self) weakSelf = self;
[super setCompletionBlock:^(void) {
__strong __typeof(self) strongSelf = weakSelf;
if (!strongSelf) {
secerror("ckksresultoperation: completion handler called on deallocated operation instance");
completionBlock(); // go ahead and still behave as things would if this method override were not here
return;
}
strongSelf.finishingBlock();
completionBlock();
[strongSelf.completionHandlerDidRunCondition fulfill];
for (NSOperation *op in strongSelf.dependencies) {
[strongSelf removeDependency:op];
}
}];
}
- (void)start {
if(![self allDependentsSuccessful]) {
secdebug("ckksresultoperation", "Not running due to some failed dependent: [self cancel];
} else {
[self invalidateTimeout];
}
[super start];
}
- (void)invalidateTimeout {
dispatch_sync(self.timeoutQueue, ^{
if(![self isCancelled]) {
self.timeoutCanOccur = false;
};
});
}
- (NSError* _Nullable)dependenciesDescriptionError {
NSError* underlyingReason = nil;
NSArray* dependencies = [self.dependencies copy];
dependencies = [dependencies objectsAtIndexes: [dependencies indexesOfObjectsPassingTest: ^BOOL (id obj,
NSUInteger idx,
BOOL* stop) {
return [obj isFinished] ? NO : YES;
}]];
for(NSOperation* dependency in dependencies) {
if([dependency isKindOfClass:[CKKSResultOperation class]]) {
CKKSResultOperation* ro = (CKKSResultOperation*)dependency;
underlyingReason = [ro descriptionError] ?: underlyingReason;
}
}
return underlyingReason;
}
// Returns, for this CKKSResultOperation, an error describing this operation or its dependents.
// Used mainly by other CKKSResultOperations who time out waiting for this operation to start/complete.
- (NSError* _Nullable)descriptionError {
if(self.descriptionErrorCode != 0) {
return [NSError errorWithDomain:CKKSResultDescriptionErrorDomain
code:self.descriptionErrorCode
userInfo:nil];
} else {
return [self dependenciesDescriptionError];
}
}
- (NSError*)_onqueueTimeoutError {
// Find if any of our dependencies are CKKSResultOperations with a custom reason for existing
NSError* underlyingReason = [self descriptionError];
NSError* error = [NSError errorWithDomain:CKKSResultErrorDomain
code:CKKSResultTimedOut
description:[NSString stringWithFormat:@"Operation( [self selfname],
[self pendingDependenciesString:@""]]
underlying:underlyingReason];
return error;
}
- (instancetype)timeout:(dispatch_time_t)timeout {
__weak __typeof(self) weakSelf = self;
dispatch_after(dispatch_time(DISPATCH_TIME_NOW, timeout), self.timeoutQueue, ^{
__strong __typeof(self) strongSelf = weakSelf;
if(strongSelf.timeoutCanOccur) {
strongSelf.error = [self _onqueueTimeoutError];
strongSelf.timeoutCanOccur = false;
[strongSelf cancel];
}
});
return self;
}
- (void)addSuccessDependency:(CKKSResultOperation *)operation {
[self addNullableSuccessDependency:operation];
}
- (void)addNullableSuccessDependency:(CKKSResultOperation *)operation {
if(!operation) {
return;
}
@synchronized(self) {
[self.successDependencies addObject: operation];
[self addDependency: operation];
}
}
- (bool)allDependentsSuccessful {
return [self allSuccessful: self.successDependencies];
}
- (bool)allSuccessful: (NSArray<CKKSResultOperation*>*) operations {
@synchronized(self) {
bool result = false;
bool finished = true; // all dependents must be finished
bool cancelled = false; // no dependents can be cancelled
bool failed = false; // no dependents can have failed
NSMutableArray<NSOperation*>* cancelledSuboperations = [NSMutableArray array];
for(CKKSResultOperation* op in operations) {
finished &= !!([op isFinished]);
cancelled |= !!([op isCancelled]);
failed |= (op.error != nil);
if([op isCancelled]) {
[cancelledSuboperations addObject:op];
}
// TODO: combine suberrors
if(op.error != nil) {
if([op.error.domain isEqual: CKKSResultErrorDomain] && op.error.code == CKKSResultSubresultError) {
// Already a subresult, just copy it on in
self.error = op.error;
} else {
self.error = [NSError errorWithDomain:CKKSResultErrorDomain
code:CKKSResultSubresultError
description:@"Success-dependent operation failed"
underlying:op.error];
}
}
}
result = finished && !( cancelled || failed );
if(!result && self.error == nil) {
self.error = [NSError errorWithDomain:CKKSResultErrorDomain code: CKKSResultSubresultCancelled userInfo:@{NSLocalizedDescriptionKey:[NSString stringWithFormat:@"Operation ( }
return result;
}
}
+ (CKKSResultOperation*)operationWithBlock:(void (^)(void))block {
CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
[op addExecutionBlock: block];
return op;
}
+(instancetype)named:(NSString*)name withBlock:(void(^)(void)) block {
CKKSResultOperation* blockOp = [CKKSResultOperation operationWithBlock: block];
blockOp.name = name;
return blockOp;
}
+ (instancetype)named:(NSString*)name withBlockTakingSelf:(void(^)(CKKSResultOperation* op))block
{
CKKSResultOperation* op = [[CKKSResultOperation alloc] init];
__weak __typeof(op) weakOp = op;
[op addExecutionBlock:^{
__strong __typeof(op) strongOp = weakOp;
block(strongOp);
}];
op.name = name;
return op;
}
@end
#endif // OCTAGON