103 lines
3.9 KiB
Objective-C
103 lines
3.9 KiB
Objective-C
#import "AsyncUtil.h"
|
|
#import "Environment.h"
|
|
#import "Constraints.h"
|
|
#import "FutureSource.h"
|
|
#import "FunctionalUtil.h"
|
|
#import "CancelTokenSource.h"
|
|
#import "TimeUtil.h"
|
|
#import "FutureUtil.h"
|
|
#import "ThreadManager.h"
|
|
#import "AsyncUtilHelperRacingOperation.h"
|
|
|
|
@implementation AsyncUtil
|
|
|
|
+(Future*) raceCancellableOperations:(NSArray*)cancellableOperationStarters
|
|
untilCancelled:(id<CancelToken>)untilCancelledToken {
|
|
|
|
require(cancellableOperationStarters != nil);
|
|
if (cancellableOperationStarters.count == 0) return [Future failed:@[]];
|
|
|
|
NSArray* racingOperations = [AsyncUtilHelperRacingOperation racingOperationsFromCancellableOperationStarters:cancellableOperationStarters
|
|
untilCancelled:untilCancelledToken];
|
|
|
|
Future* futureWinner = [AsyncUtilHelperRacingOperation asyncWinnerFromRacingOperations:racingOperations];
|
|
|
|
// cancel and terminate losers
|
|
[futureWinner thenDo:^(AsyncUtilHelperRacingOperation* winner) {
|
|
for (AsyncUtilHelperRacingOperation* contender in racingOperations) {
|
|
if (contender != winner) {
|
|
[contender cancelAndTerminate];
|
|
}
|
|
}
|
|
}];
|
|
|
|
return [futureWinner then:^(AsyncUtilHelperRacingOperation* winner) {
|
|
return [winner futureResult];
|
|
}];
|
|
}
|
|
|
|
+(Future*) raceCancellableOperation:(CancellableOperationStarter)operation
|
|
againstTimeout:(NSTimeInterval)timeoutPeriod
|
|
untilCancelled:(id<CancelToken>)untilCancelledToken {
|
|
require(operation != nil);
|
|
require(timeoutPeriod >= 0);
|
|
|
|
FutureSource* futureResultSource = [FutureSource new];
|
|
|
|
AsyncUtilHelperRacingOperation* racer = [AsyncUtilHelperRacingOperation racingOperationFromCancellableOperationStarter:operation
|
|
untilCancelled:untilCancelledToken];
|
|
[[racer futureResult] finallyDo:^(Future *completed) {
|
|
[futureResultSource trySetResult:completed];
|
|
}];
|
|
|
|
void(^tryFail)(id failure) = ^(id failure){
|
|
if ([futureResultSource trySetFailure:failure]) {
|
|
[racer cancelAndTerminate];
|
|
}
|
|
};
|
|
|
|
[TimeUtil scheduleRun:^{ tryFail([TimeoutFailure new]); }
|
|
afterDelay:timeoutPeriod
|
|
onRunLoop:[ThreadManager normalLatencyThreadRunLoop]
|
|
unlessCancelled:[futureResultSource completionAsCancelToken]];
|
|
|
|
[untilCancelledToken whenCancelled:^{ tryFail(untilCancelledToken); }];
|
|
|
|
return futureResultSource;
|
|
}
|
|
|
|
|
|
+(Future*) asyncTry:(CancellableOperationStarter)operation
|
|
upToNTimes:(NSUInteger)maxTryCount
|
|
withBaseTimeout:(NSTimeInterval)baseTimeout
|
|
andRetryFactor:(NSTimeInterval)timeoutRetryFactor
|
|
untilCancelled:(id<CancelToken>)untilCancelledToken {
|
|
|
|
require(operation != nil);
|
|
require(maxTryCount >= 0);
|
|
require(baseTimeout >= 0);
|
|
require(timeoutRetryFactor >= 0);
|
|
|
|
if (maxTryCount == 0) return [Future failed:[TimeoutFailure new]];
|
|
|
|
Future* futureResult = [AsyncUtil raceCancellableOperation:operation
|
|
againstTimeout:baseTimeout
|
|
untilCancelled:untilCancelledToken];
|
|
|
|
return [futureResult catch:^(id error) {
|
|
bool operationCancelled = [untilCancelledToken isAlreadyCancelled];
|
|
bool operationDidNotTimeout = ![error isKindOfClass:[TimeoutFailure class]];
|
|
if (operationCancelled || operationDidNotTimeout) {
|
|
return [Future failed:error];
|
|
}
|
|
|
|
return [self asyncTry:operation
|
|
upToNTimes:maxTryCount - 1
|
|
withBaseTimeout:baseTimeout * timeoutRetryFactor
|
|
andRetryFactor:timeoutRetryFactor
|
|
untilCancelled:untilCancelledToken];
|
|
}];
|
|
}
|
|
|
|
@end
|