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
6 changes: 6 additions & 0 deletions FirebaseRemoteConfig/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,3 +1,9 @@
# Unreleased
- [added] Improved how the SDK handles real-time requests when a Firebase
project has exceeded its available quota for real-time services.
Released in anticipation of future quota enforcement, this change is
designed to fetch the latest template even when the quota is exhausted.

# 11.14.0
- [fixed] Fix build warning from comparison of different enumeration types.

Expand Down
4 changes: 4 additions & 0 deletions FirebaseRemoteConfig/Sources/Private/RCNConfigSettings.h
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,10 @@
/// indicates a server issue.
- (void)updateRealtimeExponentialBackoffTime;

/// Increases the throttling time for Realtime. Should only be called if we receive a Realtime
/// retry interval in the response.
- (void)updateRealtimeBackoffTimeWithInterval:(NSTimeInterval)realtimeRetryInterval;

/// Update last active template version from last fetched template version.
- (void)updateLastActiveTemplateVersion;

Expand Down
13 changes: 13 additions & 0 deletions FirebaseRemoteConfig/Sources/RCNConfigRealtime.m
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@
/// Invalidation message field names.
static NSString *const kTemplateVersionNumberKey = @"latestTemplateVersionNumber";
static NSString *const kIsFeatureDisabled = @"featureDisabled";
static NSString *const kRealtime_Retry_Interval = @"retryIntervalSeconds";

static NSTimeInterval gTimeoutSeconds = 330;
static NSInteger const gFetchAttempts = 3;
Expand Down Expand Up @@ -521,13 +522,17 @@ - (void)autoFetch:(NSInteger)remainingAttempts targetVersion:(NSInteger)targetVe

- (void)evaluateStreamResponse:(NSDictionary *)response error:(NSError *)dataError {
NSInteger updateTemplateVersion = 1;
NSTimeInterval realtimeRetryInterval = 0;
if (dataError == nil) {
if ([response objectForKey:kTemplateVersionNumberKey]) {
updateTemplateVersion = [[response objectForKey:kTemplateVersionNumberKey] integerValue];
}
if ([response objectForKey:kIsFeatureDisabled]) {
self->_isRealtimeDisabled = [response objectForKey:kIsFeatureDisabled];
}
if ([response objectForKey:kRealtime_Retry_Interval]) {
realtimeRetryInterval = [[response objectForKey:kRealtime_Retry_Interval] integerValue];
}

if (self->_isRealtimeDisabled) {
[self pauseRealtimeStream];
Expand All @@ -544,6 +549,14 @@ - (void)evaluateStreamResponse:(NSDictionary *)response error:(NSError *)dataErr
if (updateTemplateVersion > clientTemplateVersion) {
[self autoFetch:gFetchAttempts targetVersion:updateTemplateVersion];
}

/// This field in the response indicates that the realtime request should retry after the
/// specified interval to establish a long-lived connection. This interval extends the backoff
/// duration without affecting the number of retries, so it will not enter an exponential
/// backoff state.
if (realtimeRetryInterval > 0) {
[self->_settings updateRealtimeBackoffTimeWithInterval:realtimeRetryInterval];
}
}
} else {
NSError *error =
Expand Down
10 changes: 10 additions & 0 deletions FirebaseRemoteConfig/Sources/RCNConfigSettings.m
Original file line number Diff line number Diff line change
Expand Up @@ -234,6 +234,16 @@ - (void)updateRealtimeExponentialBackoffTime {
setCurrentRealtimeThrottlingRetryIntervalSeconds:_realtimeExponentialBackoffRetryInterval];
}

/// Increase the real-time stream's backoff period from the current time plus the retry interval.
/// Any subsequent Realtime requests will be checked and allowed only if past this throttle end
/// time.
- (void)updateRealtimeBackoffTimeWithInterval:(NSTimeInterval)realtimeRetryInterval {
_realtimeExponentialBackoffThrottleEndTime =
[[NSDate date] timeIntervalSince1970] + realtimeRetryInterval;

[_userDefaultsManager setRealtimeThrottleEndTime:_realtimeExponentialBackoffThrottleEndTime];
}

- (void)setRealtimeRetryCount:(int)realtimeRetryCount {
_realtimeRetryCount = realtimeRetryCount;
[_userDefaultsManager setRealtimeRetryCount:_realtimeRetryCount];
Expand Down
31 changes: 31 additions & 0 deletions FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m
Original file line number Diff line number Diff line change
Expand Up @@ -1651,7 +1651,7 @@
dispatch_get_main_queue(), ^{
OCMVerify([self->_configRealtime[i] addConfigUpdateListener:completion]);
OCMVerify([self->_configRealtime[i] removeConfigUpdateListener:completion]);
OCMVerify([self->_configRealtime[i] pauseRealtimeStream]);

Check failure on line 1654 in FirebaseRemoteConfig/Tests/Unit/RCNRemoteConfigTest.m

View workflow job for this annotation

GitHub Actions / spm_1 / spm (macos-15, Xcode_16.4, visionOS)

testRemoveRealtimeListener, OCPartialMockObject(RCNConfigRealtime): Method `pauseRealtimeStream` was not invoked; but was expected at least once.
[expectations[i] fulfill];
});

Expand Down Expand Up @@ -1785,6 +1785,37 @@
}
}

- (void)testRealtimeUpdatesBackoffMetadataWhenRetryIntervalIsProvided {
NSMutableArray<XCTestExpectation *> *expectations =
[[NSMutableArray alloc] initWithCapacity:RCNTestRCNumTotalInstances];
for (int i = 0; i < RCNTestRCNumTotalInstances; i++) {
expectations[i] =
[self expectationWithDescription:
[NSString stringWithFormat:@"Test backoff metadata updates with a provided retry "
@"interval in the stream response - instance %d",
i]];
NSTimeInterval realtimeRetryInterval = 240;
NSMutableDictionary *dictionary = [[NSMutableDictionary alloc] init];
[dictionary setValue:@"1" forKey:@"latestTemplateVersionNumber"];
[dictionary setValue:@(realtimeRetryInterval) forKey:@"retryIntervalSeconds"];

NSTimeInterval expectedThrottleEndTime =
[[NSDate date] timeIntervalSince1970] + realtimeRetryInterval;

[_configRealtime[i] evaluateStreamResponse:dictionary error:nil];
dispatch_after(
dispatch_time(DISPATCH_TIME_NOW, (int64_t)(_checkCompletionTimeout * NSEC_PER_SEC)),
dispatch_get_main_queue(), ^{
NSTimeInterval retrievedThrottleEndTime =
self->_configInstances[i].settings.realtimeExponentialBackoffThrottleEndTime;
XCTAssertEqualWithAccuracy(retrievedThrottleEndTime, expectedThrottleEndTime, 1.0);
[expectations[i] fulfill];
});

[self waitForExpectationsWithTimeout:_expectationTimeout handler:nil];
}
}

- (void)testRealtimeStreamRequestBody {
XCTestExpectation *requestBodyExpectation = [self expectationWithDescription:@"requestBody"];
__block NSData *requestBody;
Expand Down
Loading