session-ios/Signal/src/async/AsyncUtil.m

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