aboutsummaryrefslogtreecommitdiffhomepage
path: root/Firebase/Database/Core/Utilities/FIRRetryHelper.m
diff options
context:
space:
mode:
authorGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
committerGravatar Paul Beusterien <paulbeusterien@google.com>2017-05-15 12:27:07 -0700
commit98ba64449a632518bd2b86fe8d927f4a960d3ddc (patch)
tree131d9c4272fa6179fcda6c5a33fcb3b1bd57ad2e /Firebase/Database/Core/Utilities/FIRRetryHelper.m
parent32461366c9e204a527ca05e6e9b9404a2454ac51 (diff)
Initial
Diffstat (limited to 'Firebase/Database/Core/Utilities/FIRRetryHelper.m')
-rw-r--r--Firebase/Database/Core/Utilities/FIRRetryHelper.m139
1 files changed, 139 insertions, 0 deletions
diff --git a/Firebase/Database/Core/Utilities/FIRRetryHelper.m b/Firebase/Database/Core/Utilities/FIRRetryHelper.m
new file mode 100644
index 0000000..199e17d
--- /dev/null
+++ b/Firebase/Database/Core/Utilities/FIRRetryHelper.m
@@ -0,0 +1,139 @@
+/*
+ * Copyright 2017 Google
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+#import "FIRRetryHelper.h"
+#import "FUtilities.h"
+
+@interface FIRRetryHelperTask : NSObject
+
+@property (nonatomic, strong) void (^block)();
+
+@end
+
+@implementation FIRRetryHelperTask
+
+- (instancetype) initWithBlock:(void (^)())block {
+ self = [super init];
+ if (self != nil) {
+ self->_block = [block copy];
+ }
+ return self;
+}
+
+- (BOOL) isCanceled {
+ return self.block == nil;
+}
+
+- (void) cancel {
+ self.block = nil;
+}
+
+- (void) execute {
+ if (self.block) {
+ self.block();
+ }
+}
+
+@end
+
+
+
+@interface FIRRetryHelper ()
+
+@property (nonatomic, strong) dispatch_queue_t dispatchQueue;
+@property (nonatomic) NSTimeInterval minRetryDelayAfterFailure;
+@property (nonatomic) NSTimeInterval maxRetryDelay;
+@property (nonatomic) double retryExponent;
+@property (nonatomic) double jitterFactor;
+
+@property (nonatomic) BOOL lastWasSuccess;
+@property (nonatomic) NSTimeInterval currentRetryDelay;
+
+@property (nonatomic, strong) FIRRetryHelperTask *scheduledRetry;
+
+@end
+
+@implementation FIRRetryHelper
+
+- (instancetype) initWithDispatchQueue:(dispatch_queue_t)dispatchQueue
+ minRetryDelayAfterFailure:(NSTimeInterval)minRetryDelayAfterFailure
+ maxRetryDelay:(NSTimeInterval)maxRetryDelay
+ retryExponent:(double)retryExponent
+ jitterFactor:(double)jitterFactor {
+ self = [super init];
+ if (self != nil) {
+ self->_dispatchQueue = dispatchQueue;
+ self->_minRetryDelayAfterFailure = minRetryDelayAfterFailure;
+ self->_maxRetryDelay = maxRetryDelay;
+ self->_retryExponent = retryExponent;
+ self->_jitterFactor = jitterFactor;
+ self->_lastWasSuccess = YES;
+ }
+ return self;
+}
+
+- (void) retry:(void (^)())block {
+ if (self.scheduledRetry != nil) {
+ FFLog(@"I-RDB054001", @"Canceling existing retry attempt");
+ [self.scheduledRetry cancel];
+ self.scheduledRetry = nil;
+ }
+
+ NSTimeInterval delay;
+ if (self.lastWasSuccess) {
+ delay = 0;
+ } else {
+ if (self.currentRetryDelay == 0) {
+ self.currentRetryDelay = self.minRetryDelayAfterFailure;
+ } else {
+ NSTimeInterval newDelay = (self.currentRetryDelay * self.retryExponent);
+ self.currentRetryDelay = MIN(newDelay, self.maxRetryDelay);
+ }
+
+ delay = ((1 - self.jitterFactor) * self.currentRetryDelay) +
+ (self.jitterFactor * self.currentRetryDelay * [FUtilities randomDouble]);
+ FFLog(@"I-RDB054002", @"Scheduling retry in %fs", delay);
+
+ }
+ self.lastWasSuccess = NO;
+ FIRRetryHelperTask *task = [[FIRRetryHelperTask alloc] initWithBlock:block];
+ self.scheduledRetry = task;
+ dispatch_time_t popTime = dispatch_time(DISPATCH_TIME_NOW, (long long)(delay * NSEC_PER_SEC));
+ dispatch_after(popTime, self.dispatchQueue, ^{
+ if (![task isCanceled]) {
+ self.scheduledRetry = nil;
+ [task execute];
+ }
+ });
+}
+
+- (void) signalSuccess {
+ self.lastWasSuccess = YES;
+ self.currentRetryDelay = 0;
+}
+
+- (void) cancel {
+ if (self.scheduledRetry != nil) {
+ FFLog(@"I-RDB054003", @"Canceling existing retry attempt");
+ [self.scheduledRetry cancel];
+ self.scheduledRetry = nil;
+ } else {
+ FFLog(@"I-RDB054004", @"No existing retry attempt to cancel");
+ }
+ self.currentRetryDelay = 0;
+}
+
+@end