Skip to content

Commit 8e60b6f

Browse files
authored
Merge pull request #390 from Countly/back_off_mech
feat: back of mech
2 parents cedf27a + a9ac85b commit 8e60b6f

File tree

7 files changed

+182
-29
lines changed

7 files changed

+182
-29
lines changed

CHANGELOG.md

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@
22
* Adding SDK health check requests after init
33
* The feedback widgets now have fullscreen and transparent backgrounds for a cleaner look.
44
* Added a config method to disable server config in the initialization "disableSDKBehaviorSettingsUpdates()".
5+
* Improved request queue handling, added a backoff mechanism to the SDK to better handle cases where the server responds slowly, enabled by default.
6+
* Added a config method to disable backoff mechanism "disableBackoffMechanism"
57

68
## 25.4.1
79
* Mitigated an issue that could occur while serializing events to improve stability, performance and memory usage.

CountlyConfig.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -680,6 +680,11 @@ typedef enum : NSUInteger
680680
*/
681681
@property(nonatomic) BOOL disableSDKBehaviorSettingsUpdates;
682682

683+
/**
684+
* Will disable back off mechanism
685+
*/
686+
@property (nonatomic) BOOL disableBackoffMechanism;
687+
683688
#if (TARGET_OS_IOS)
684689
/**
685690
* Variable to access content configurations.

CountlyConnectionManager.m

Lines changed: 73 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@
55
// Please visit www.count.ly for more information.
66

77
#import "CountlyCommon.h"
8+
#import <stdatomic.h>
89

910
@interface CountlyConnectionManager ()
1011
{
@@ -16,6 +17,8 @@ @interface CountlyConnectionManager ()
1617
@property (nonatomic) NSURLSession* URLSession;
1718

1819
@property (nonatomic, strong) NSDate *startTime;
20+
@property (nonatomic, assign) atomic_bool backoff;
21+
1922

2023
@end
2124

@@ -82,6 +85,7 @@ @interface CountlyConnectionManager ()
8285
NSString* const kCountlyEndpointSurveys = @"/surveys";
8386

8487
const NSInteger kCountlyGETRequestMaxLength = 2048;
88+
static const NSTimeInterval CONNECTION_TIMEOUT = 30.0;
8589

8690
@implementation CountlyConnectionManager : NSObject
8791

@@ -101,6 +105,7 @@ - (instancetype)init
101105
{
102106
unsentSessionLength = 0.0;
103107
isSessionStarted = NO;
108+
atomic_init(&_backoff, NO);
104109
}
105110

106111
return self;
@@ -176,6 +181,12 @@ - (void)proceedOnQueue
176181
return;
177182
}
178183

184+
BOOL backoffFlag = atomic_load(&_backoff) ? YES : NO;
185+
if (backoffFlag) {
186+
CLY_LOG_I(@"%s, currently backed off, skipping proceeding the queue", __FUNCTION__);
187+
return;
188+
}
189+
179190
if (!self.startTime) {
180191
self.startTime = [NSDate date]; // Record start time only when it's not already recorded
181192
CLY_LOG_D(@"Proceeding on queue started, queued request count %lu", [CountlyPersistency.sharedInstance remainingRequestCount]);
@@ -212,6 +223,8 @@ - (void)proceedOnQueue
212223
return;
213224
}
214225

226+
_URLSessionConfiguration.timeoutIntervalForRequest = CONNECTION_TIMEOUT;
227+
_URLSessionConfiguration.timeoutIntervalForResource = CONNECTION_TIMEOUT;
215228
NSString* queryString = firstItemInQueue;
216229
NSString* endPoint = kCountlyEndpointI;
217230

@@ -271,11 +284,12 @@ - (void)proceedOnQueue
271284
}
272285

273286
request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;
274-
287+
NSDate *startTimeRequest = [NSDate date];
275288
self.connection = [self.URLSession dataTaskWithRequest:request completionHandler:^(NSData * data, NSURLResponse * response, NSError * error)
276289
{
277290
self.connection = nil;
278-
291+
NSDate *endTimeRequest = [NSDate date];
292+
long duration = (long)[endTimeRequest timeIntervalSinceDate:startTimeRequest];
279293

280294
CLY_LOG_V(@"Approximate received data size for request <%p> is %ld bytes.", (id)request, (long)data.length);
281295

@@ -294,8 +308,15 @@ - (void)proceedOnQueue
294308
[CountlyPersistency.sharedInstance removeFromQueue:firstItemInQueue];
295309

296310
[CountlyPersistency.sharedInstance saveToFile];
297-
298-
[self proceedOnQueue];
311+
312+
if(CountlyServerConfig.sharedInstance.backoffMechanism && [self backoff:duration queryString:queryString]){
313+
CLY_LOG_D(@"%s, backed off dropping proceeding the queue", __FUNCTION__);
314+
self.startTime = nil;
315+
[self backoffCountdown];
316+
} else {
317+
[self proceedOnQueue];
318+
319+
}
299320
}
300321
else
301322
{
@@ -320,6 +341,54 @@ - (void)proceedOnQueue
320341
[self logRequest:request];
321342
}
322343

344+
- (BOOL)backoff:(long)responseTimeSeconds queryString:(NSString *)queryString
345+
{
346+
BOOL result = NO;
347+
// Check if the current response time is within acceptable limits
348+
if (responseTimeSeconds >= [CountlyServerConfig.sharedInstance bomAcceptedTimeoutSeconds]) {
349+
// Check if the remaining request count is within acceptable limits
350+
NSUInteger remainingRequests = [CountlyPersistency.sharedInstance remainingRequestCount];
351+
NSUInteger threshold = (NSUInteger)(CountlyPersistency.sharedInstance.storedRequestsLimit * [CountlyServerConfig.sharedInstance bomRQPercentage]);
352+
353+
if (remainingRequests <= threshold) {
354+
// Calculate the age of the current request
355+
double requestTimestamp = [[queryString cly_valueForQueryStringKey:kCountlyQSKeyTimestamp] longLongValue] / 1000.0;
356+
double requestAgeInSeconds = [NSDate date].timeIntervalSince1970 - requestTimestamp;
357+
358+
if (requestAgeInSeconds <= [CountlyServerConfig.sharedInstance bomRequestAge] * 3600.0) {
359+
// Server is too busy, back off
360+
result = YES;
361+
[CountlyHealthTracker.sharedInstance logBackoffRequest];
362+
}
363+
}
364+
}
365+
366+
if (!result) {
367+
[CountlyHealthTracker.sharedInstance logConsecutiveBackoffRequest];
368+
}
369+
370+
return result;
371+
}
372+
373+
- (void)backoffCountdown
374+
{
375+
__weak typeof(self) weakSelf = self;
376+
CLY_LOG_D(@"%s, backed off, countdown start for %f seconds", __FUNCTION__, [CountlyServerConfig.sharedInstance bomDuration]);
377+
378+
atomic_store(&_backoff, YES);
379+
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)([CountlyServerConfig.sharedInstance bomDuration] * NSEC_PER_SEC));
380+
dispatch_after(delay, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
381+
__strong typeof(weakSelf) strongSelf = weakSelf;
382+
if (!strongSelf) return;
383+
384+
385+
CLY_LOG_D(@"%s, countdown finished, running tick in background thread", __FUNCTION__);
386+
atomic_store(&strongSelf->_backoff, NO);
387+
[strongSelf proceedOnQueue];
388+
});
389+
}
390+
391+
323392
- (NSString*)extractAndRemoveOverrideEndPoint:(NSString **)queryString
324393
{
325394
if([*queryString containsString:kCountlyNewEndPoint]) {

CountlyHealthTracker.h

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,8 @@
2020

2121
- (void)logBackoffRequest;
2222

23+
- (void)logConsecutiveBackoffRequest;
24+
2325
- (void)clearAndSave;
2426

2527
- (void)saveState;

CountlyHealthTracker.m

Lines changed: 20 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,8 @@ @interface CountlyHealthTracker ()
1515
@property (nonatomic, assign) long countLogWarning;
1616
@property (nonatomic, assign) long countLogError;
1717
@property (nonatomic, assign) long countBackoffRequest;
18+
@property (nonatomic, assign) long countConsecutiveBackoffRequest;
19+
@property (nonatomic, assign) long consecutiveBackoffRequest;
1820
@property (nonatomic, assign) NSInteger statusCode;
1921
@property (nonatomic, strong) NSString *errorMessage;
2022
@property (nonatomic, assign) BOOL healthCheckEnabled;
@@ -29,12 +31,14 @@ @implementation CountlyHealthTracker
2931
NSString * const keyStatusCode = @"RStatC";
3032
NSString * const keyErrorMessage = @"REMsg";
3133
NSString * const keyBackoffRequest = @"BReq";
34+
NSString * const keyConsecutiveBackoffRequest = @"CBReq";
3235

3336
NSString * const requestKeyErrorCount = @"el";
3437
NSString * const requestKeyWarningCount = @"wl";
3538
NSString * const requestKeyStatusCode = @"sc";
3639
NSString * const requestKeyRequestError = @"em";
37-
NSString * const requestKeyBackoffRequest = @"br";
40+
NSString * const requestKeyBackoffRequest = @"bom";
41+
NSString * const requestKeyConsecutiveBackoffRequest = @"cbom";
3842

3943
+ (instancetype)sharedInstance {
4044
static CountlyHealthTracker *instance = nil;
@@ -69,6 +73,7 @@ - (void)setupInitialCounters:(NSDictionary *)initialState {
6973
self.statusCode = [initialState[keyStatusCode] integerValue];
7074
self.errorMessage = initialState[keyErrorMessage] ?: @"";
7175
self.countBackoffRequest = [initialState[keyBackoffRequest] longValue];
76+
self.consecutiveBackoffRequest = [initialState[keyConsecutiveBackoffRequest] longValue];
7277

7378
CLY_LOG_D(@"%s, Loaded initial health check state: [%@]", __FUNCTION__, initialState);
7479
}
@@ -97,6 +102,12 @@ - (void)logFailedNetworkRequestWithStatusCode:(NSInteger)statusCode
97102

98103
- (void)logBackoffRequest {
99104
self.countBackoffRequest++;
105+
self.countConsecutiveBackoffRequest++;
106+
}
107+
108+
- (void)logConsecutiveBackoffRequest {
109+
self.consecutiveBackoffRequest = MAX(self.consecutiveBackoffRequest, self.countConsecutiveBackoffRequest);
110+
self.countConsecutiveBackoffRequest = 0;
100111
}
101112

102113
- (void)clearAndSave {
@@ -105,12 +116,15 @@ - (void)clearAndSave {
105116
}
106117

107118
- (void)saveState {
119+
[self logConsecutiveBackoffRequest];
120+
108121
NSDictionary *healthCheckState = @{
109122
keyLogWarning: @(self.countLogWarning),
110123
keyLogError: @(self.countLogError),
111124
keyStatusCode: @(self.statusCode),
112125
keyErrorMessage: self.errorMessage ?: @"",
113-
keyBackoffRequest: @(self.countBackoffRequest)
126+
keyBackoffRequest: @(self.countBackoffRequest),
127+
keyConsecutiveBackoffRequest: @(self.consecutiveBackoffRequest)
114128
};
115129

116130
[CountlyPersistency.sharedInstance storeHealthCheckTrackerState:healthCheckState];
@@ -124,6 +138,8 @@ - (void)clearValues {
124138
self.statusCode = -1;
125139
self.errorMessage = @"";
126140
self.countBackoffRequest = 0;
141+
self.consecutiveBackoffRequest = 0;
142+
self.countConsecutiveBackoffRequest = 0;
127143
}
128144

129145
- (void)sendHealthCheck {
@@ -186,7 +202,8 @@ - (NSURLRequest *)healthCheckRequest {
186202
requestKeyWarningCount: @(self.countLogWarning),
187203
requestKeyStatusCode: @(self.statusCode),
188204
requestKeyRequestError: self.errorMessage ?: @"",
189-
requestKeyBackoffRequest: @(self.countBackoffRequest)
205+
requestKeyBackoffRequest: @(self.countBackoffRequest),
206+
requestKeyConsecutiveBackoffRequest: @(self.consecutiveBackoffRequest)
190207
}]];
191208

192209
queryString = [queryString stringByAppendingFormat:@"&%@=%@", @"metrics", [self dictionaryToJsonString:@{

CountlyServerConfig.h

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,5 +39,10 @@ extern NSString* const kCountlySCKeySC;
3939
- (BOOL)sessionTrackingEnabled;
4040
- (BOOL)locationTrackingEnabled;
4141
- (BOOL)refreshContentZoneEnabled;
42+
- (BOOL)backoffMechanism;
43+
- (NSInteger)bomAcceptedTimeoutSeconds;
44+
- (double)bomRQPercentage;
45+
- (NSInteger)bomRequestAge;
46+
- (NSInteger)bomDuration;
4247
@end
4348

0 commit comments

Comments
 (0)