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 ()
8285NSString * const kCountlyEndpointSurveys = @" /surveys" ;
8386
8487const 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 ]) {
0 commit comments