Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@
* Adding SDK health check requests after init
* The feedback widgets now have fullscreen and transparent backgrounds for a cleaner look.
* Added a config method to disable server config in the initialization "disableSDKBehaviorSettingsUpdates()".
* Improved request queue handling, added a backoff mechanism to the SDK to better handle cases where the server responds slowly, enabled by default.
* Added a config method to disable backoff mechanism "disableBackoffMechanism"

## 25.4.1
* Mitigated an issue that could occur while serializing events to improve stability, performance and memory usage.
Expand Down
5 changes: 5 additions & 0 deletions CountlyConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -680,6 +680,11 @@ typedef enum : NSUInteger
*/
@property(nonatomic) BOOL disableSDKBehaviorSettingsUpdates;

/**
* Will disable back off mechanism
*/
@property (nonatomic) BOOL disableBackoffMechanism;

#if (TARGET_OS_IOS)
/**
* Variable to access content configurations.
Expand Down
77 changes: 73 additions & 4 deletions CountlyConnectionManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
// Please visit www.count.ly for more information.

#import "CountlyCommon.h"
#import <stdatomic.h>

@interface CountlyConnectionManager ()
{
Expand All @@ -16,6 +17,8 @@ @interface CountlyConnectionManager ()
@property (nonatomic) NSURLSession* URLSession;

@property (nonatomic, strong) NSDate *startTime;
@property (nonatomic, assign) atomic_bool backoff;


@end

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

const NSInteger kCountlyGETRequestMaxLength = 2048;
static const NSTimeInterval CONNECTION_TIMEOUT = 30.0;

@implementation CountlyConnectionManager : NSObject

Expand All @@ -101,6 +105,7 @@ - (instancetype)init
{
unsentSessionLength = 0.0;
isSessionStarted = NO;
atomic_init(&_backoff, NO);
}

return self;
Expand Down Expand Up @@ -176,6 +181,12 @@ - (void)proceedOnQueue
return;
}

BOOL backoffFlag = atomic_load(&_backoff) ? YES : NO;
if (backoffFlag) {
CLY_LOG_I(@"%s, currently backed off, skipping proceeding the queue", __FUNCTION__);
return;
}

if (!self.startTime) {
self.startTime = [NSDate date]; // Record start time only when it's not already recorded
CLY_LOG_D(@"Proceeding on queue started, queued request count %lu", [CountlyPersistency.sharedInstance remainingRequestCount]);
Expand Down Expand Up @@ -212,6 +223,8 @@ - (void)proceedOnQueue
return;
}

_URLSessionConfiguration.timeoutIntervalForRequest = CONNECTION_TIMEOUT;
_URLSessionConfiguration.timeoutIntervalForResource = CONNECTION_TIMEOUT;
NSString* queryString = firstItemInQueue;
NSString* endPoint = kCountlyEndpointI;

Expand Down Expand Up @@ -271,11 +284,12 @@ - (void)proceedOnQueue
}

request.cachePolicy = NSURLRequestReloadIgnoringLocalCacheData;

NSDate *startTimeRequest = [NSDate date];
self.connection = [self.URLSession dataTaskWithRequest:request completionHandler:^(NSData * data, NSURLResponse * response, NSError * error)
{
self.connection = nil;

NSDate *endTimeRequest = [NSDate date];
long duration = (long)[endTimeRequest timeIntervalSinceDate:startTimeRequest];

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

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

[CountlyPersistency.sharedInstance saveToFile];

[self proceedOnQueue];

if(CountlyServerConfig.sharedInstance.backoffMechanism && [self backoff:duration queryString:queryString]){
CLY_LOG_D(@"%s, backed off dropping proceeding the queue", __FUNCTION__);
self.startTime = nil;
[self backoffCountdown];
} else {
[self proceedOnQueue];

}
}
else
{
Expand All @@ -320,6 +341,54 @@ - (void)proceedOnQueue
[self logRequest:request];
}

- (BOOL)backoff:(long)responseTimeSeconds queryString:(NSString *)queryString
{
BOOL result = NO;
// Check if the current response time is within acceptable limits
if (responseTimeSeconds >= [CountlyServerConfig.sharedInstance bomAcceptedTimeoutSeconds]) {
// Check if the remaining request count is within acceptable limits
NSUInteger remainingRequests = [CountlyPersistency.sharedInstance remainingRequestCount];
NSUInteger threshold = (NSUInteger)(CountlyPersistency.sharedInstance.storedRequestsLimit * [CountlyServerConfig.sharedInstance bomRQPercentage]);

if (remainingRequests <= threshold) {
// Calculate the age of the current request
double requestTimestamp = [[queryString cly_valueForQueryStringKey:kCountlyQSKeyTimestamp] longLongValue] / 1000.0;
double requestAgeInSeconds = [NSDate date].timeIntervalSince1970 - requestTimestamp;

if (requestAgeInSeconds <= [CountlyServerConfig.sharedInstance bomRequestAge] * 3600.0) {
// Server is too busy, back off
result = YES;
[CountlyHealthTracker.sharedInstance logBackoffRequest];
}
}
}

if (!result) {
[CountlyHealthTracker.sharedInstance logConsecutiveBackoffRequest];
}

return result;
}

- (void)backoffCountdown
{
__weak typeof(self) weakSelf = self;
CLY_LOG_D(@"%s, backed off, countdown start for %f seconds", __FUNCTION__, [CountlyServerConfig.sharedInstance bomDuration]);

atomic_store(&_backoff, YES);
dispatch_time_t delay = dispatch_time(DISPATCH_TIME_NOW, (int64_t)([CountlyServerConfig.sharedInstance bomDuration] * NSEC_PER_SEC));
dispatch_after(delay, dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
__strong typeof(weakSelf) strongSelf = weakSelf;
if (!strongSelf) return;


CLY_LOG_D(@"%s, countdown finished, running tick in background thread", __FUNCTION__);
atomic_store(&strongSelf->_backoff, NO);
[strongSelf proceedOnQueue];
});
}


- (NSString*)extractAndRemoveOverrideEndPoint:(NSString **)queryString
{
if([*queryString containsString:kCountlyNewEndPoint]) {
Expand Down
2 changes: 2 additions & 0 deletions CountlyHealthTracker.h
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@

- (void)logBackoffRequest;

- (void)logConsecutiveBackoffRequest;

- (void)clearAndSave;

- (void)saveState;
Expand Down
23 changes: 20 additions & 3 deletions CountlyHealthTracker.m
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,8 @@ @interface CountlyHealthTracker ()
@property (nonatomic, assign) long countLogWarning;
@property (nonatomic, assign) long countLogError;
@property (nonatomic, assign) long countBackoffRequest;
@property (nonatomic, assign) long countConsecutiveBackoffRequest;
@property (nonatomic, assign) long consecutiveBackoffRequest;
@property (nonatomic, assign) NSInteger statusCode;
@property (nonatomic, strong) NSString *errorMessage;
@property (nonatomic, assign) BOOL healthCheckEnabled;
Expand All @@ -29,12 +31,14 @@ @implementation CountlyHealthTracker
NSString * const keyStatusCode = @"RStatC";
NSString * const keyErrorMessage = @"REMsg";
NSString * const keyBackoffRequest = @"BReq";
NSString * const keyConsecutiveBackoffRequest = @"CBReq";

NSString * const requestKeyErrorCount = @"el";
NSString * const requestKeyWarningCount = @"wl";
NSString * const requestKeyStatusCode = @"sc";
NSString * const requestKeyRequestError = @"em";
NSString * const requestKeyBackoffRequest = @"br";
NSString * const requestKeyBackoffRequest = @"bom";
NSString * const requestKeyConsecutiveBackoffRequest = @"cbom";

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

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

- (void)logBackoffRequest {
self.countBackoffRequest++;
self.countConsecutiveBackoffRequest++;
}

- (void)logConsecutiveBackoffRequest {
self.consecutiveBackoffRequest = MAX(self.consecutiveBackoffRequest, self.countConsecutiveBackoffRequest);
self.countConsecutiveBackoffRequest = 0;
}

- (void)clearAndSave {
Expand All @@ -105,12 +116,15 @@ - (void)clearAndSave {
}

- (void)saveState {
[self logConsecutiveBackoffRequest];

NSDictionary *healthCheckState = @{
keyLogWarning: @(self.countLogWarning),
keyLogError: @(self.countLogError),
keyStatusCode: @(self.statusCode),
keyErrorMessage: self.errorMessage ?: @"",
keyBackoffRequest: @(self.countBackoffRequest)
keyBackoffRequest: @(self.countBackoffRequest),
keyConsecutiveBackoffRequest: @(self.consecutiveBackoffRequest)
};

[CountlyPersistency.sharedInstance storeHealthCheckTrackerState:healthCheckState];
Expand All @@ -124,6 +138,8 @@ - (void)clearValues {
self.statusCode = -1;
self.errorMessage = @"";
self.countBackoffRequest = 0;
self.consecutiveBackoffRequest = 0;
self.countConsecutiveBackoffRequest = 0;
}

- (void)sendHealthCheck {
Expand Down Expand Up @@ -186,7 +202,8 @@ - (NSURLRequest *)healthCheckRequest {
requestKeyWarningCount: @(self.countLogWarning),
requestKeyStatusCode: @(self.statusCode),
requestKeyRequestError: self.errorMessage ?: @"",
requestKeyBackoffRequest: @(self.countBackoffRequest)
requestKeyBackoffRequest: @(self.countBackoffRequest),
requestKeyConsecutiveBackoffRequest: @(self.consecutiveBackoffRequest)
}]];

queryString = [queryString stringByAppendingFormat:@"&%@=%@", @"metrics", [self dictionaryToJsonString:@{
Expand Down
5 changes: 5 additions & 0 deletions CountlyServerConfig.h
Original file line number Diff line number Diff line change
Expand Up @@ -39,5 +39,10 @@ extern NSString* const kCountlySCKeySC;
- (BOOL)sessionTrackingEnabled;
- (BOOL)locationTrackingEnabled;
- (BOOL)refreshContentZoneEnabled;
- (BOOL)backoffMechanism;
- (NSInteger)bomAcceptedTimeoutSeconds;
- (double)bomRQPercentage;
- (NSInteger)bomRequestAge;
- (NSInteger)bomDuration;
@end

Loading
Loading