Skip to content

Commit d717fbd

Browse files
Merge 7bce25c into acf250d
2 parents acf250d + 7bce25c commit d717fbd

File tree

5 files changed

+80
-4
lines changed

5 files changed

+80
-4
lines changed

firebase-config/CHANGELOG.md

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,8 @@
11
# Unreleased
2-
2+
* [changed] This update introduces improvements to how the SDK handles real-time requests when a
3+
Firebase project has exceeded its available quota for real-time services. Released in anticipation
4+
of future quota enforcement, this change is designed to fetch the latest template even when the
5+
quota is exhausted.
36

47
# 22.1.2
58
* [fixed] Fixed `NetworkOnMainThreadException` on Android versions below 8 by disconnecting

firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigAutoFetch.java

Lines changed: 30 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -19,6 +19,8 @@
1919
import android.util.Log;
2020
import androidx.annotation.GuardedBy;
2121
import androidx.annotation.VisibleForTesting;
22+
import com.google.android.gms.common.util.Clock;
23+
import com.google.android.gms.common.util.DefaultClock;
2224
import com.google.android.gms.tasks.Task;
2325
import com.google.android.gms.tasks.Tasks;
2426
import com.google.firebase.remoteconfig.ConfigUpdate;
@@ -31,6 +33,7 @@
3133
import java.io.InputStream;
3234
import java.io.InputStreamReader;
3335
import java.net.HttpURLConnection;
36+
import java.util.Date;
3437
import java.util.Random;
3538
import java.util.Set;
3639
import java.util.concurrent.ScheduledExecutorService;
@@ -43,6 +46,7 @@ public class ConfigAutoFetch {
4346
private static final int MAXIMUM_FETCH_ATTEMPTS = 3;
4447
private static final String TEMPLATE_VERSION_KEY = "latestTemplateVersionNumber";
4548
private static final String REALTIME_DISABLED_KEY = "featureDisabled";
49+
private static final String REALTIME_RETRY_INTERVAL = "retryIntervalSeconds";
4650

4751
@GuardedBy("this")
4852
private final Set<ConfigUpdateListener> eventListeners;
@@ -54,6 +58,8 @@ public class ConfigAutoFetch {
5458
private final ConfigUpdateListener retryCallback;
5559
private final ScheduledExecutorService scheduledExecutorService;
5660
private final Random random;
61+
private final Clock clock;
62+
private final ConfigSharedPrefsClient sharedPrefsClient;
5763
private boolean isInBackground;
5864

5965
public ConfigAutoFetch(
@@ -62,7 +68,8 @@ public ConfigAutoFetch(
6268
ConfigCacheClient activatedCache,
6369
Set<ConfigUpdateListener> eventListeners,
6470
ConfigUpdateListener retryCallback,
65-
ScheduledExecutorService scheduledExecutorService) {
71+
ScheduledExecutorService scheduledExecutorService,
72+
ConfigSharedPrefsClient sharedPrefsClient) {
6673
this.httpURLConnection = httpURLConnection;
6774
this.configFetchHandler = configFetchHandler;
6875
this.activatedCache = activatedCache;
@@ -71,6 +78,19 @@ public ConfigAutoFetch(
7178
this.scheduledExecutorService = scheduledExecutorService;
7279
this.random = new Random();
7380
this.isInBackground = false;
81+
this.sharedPrefsClient = sharedPrefsClient;
82+
this.clock = DefaultClock.getInstance();
83+
}
84+
85+
// Increase the backoff duration with a new end time based on Retry Interval
86+
private synchronized void updateBackoffMetadataWithRetryInterval(
87+
int realtimeRetryIntervalInSeconds) {
88+
Date currentTime = new Date(clock.currentTimeMillis());
89+
long backoffDurationInMillis = realtimeRetryIntervalInSeconds * 1000L;
90+
Date backoffEndTime = new Date(currentTime.getTime() + backoffDurationInMillis);
91+
92+
// Persist the new values to disk-backed metadata.
93+
sharedPrefsClient.setRealtimeBackoffEndTime(backoffEndTime);
7494
}
7595

7696
private synchronized void propagateErrors(FirebaseRemoteConfigException exception) {
@@ -190,6 +210,15 @@ private void handleNotifications(InputStream inputStream) throws IOException {
190210
autoFetch(MAXIMUM_FETCH_ATTEMPTS, targetTemplateVersion);
191211
}
192212
}
213+
214+
// This field in the response indicates that the realtime request should retry after the
215+
// specified interval to establish a long-lived connection. This interval extends the
216+
// backoff duration without affecting the number of retries, so it will not enter an
217+
// exponential backoff state.
218+
if (jsonObject.has(REALTIME_RETRY_INTERVAL)) {
219+
int realtimeRetryIntervalInSeconds = jsonObject.getInt(REALTIME_RETRY_INTERVAL);
220+
updateBackoffMetadataWithRetryInterval(realtimeRetryIntervalInSeconds);
221+
}
193222
} catch (JSONException ex) {
194223
// Message was mangled up and so it was unable to be parsed. User is notified of this
195224
// because it there could be a new configuration that needs to be fetched.

firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigRealtimeHttpClient.java

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -469,7 +469,8 @@ public void onError(@NonNull FirebaseRemoteConfigException error) {
469469
activatedCache,
470470
listeners,
471471
retryCallback,
472-
scheduledExecutorService);
472+
scheduledExecutorService,
473+
sharedPrefsClient);
473474
}
474475

475476
// HTTP status code that the Realtime client should retry on.

firebase-config/src/main/java/com/google/firebase/remoteconfig/internal/ConfigSharedPrefsClient.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -394,6 +394,16 @@ void setRealtimeBackoffMetadata(int numFailedStreams, Date backoffEndTime) {
394394
}
395395
}
396396

397+
@VisibleForTesting
398+
public void setRealtimeBackoffEndTime(Date backoffEndTime) {
399+
synchronized (realtimeBackoffMetadataLock) {
400+
frcSharedPrefs
401+
.edit()
402+
.putLong(REALTIME_BACKOFF_END_TIME_IN_MILLIS_KEY, backoffEndTime.getTime())
403+
.apply();
404+
}
405+
}
406+
397407
void resetRealtimeBackoff() {
398408
setRealtimeBackoffMetadata(NO_FAILED_REALTIME_STREAMS, NO_BACKOFF_TIME);
399409
}

firebase-config/src/test/java/com/google/firebase/remoteconfig/FirebaseRemoteConfigTest.java

Lines changed: 34 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,6 +50,8 @@
5050
import android.os.Bundle;
5151
import androidx.annotation.NonNull;
5252
import androidx.test.core.app.ApplicationProvider;
53+
import com.google.android.gms.common.util.Clock;
54+
import com.google.android.gms.common.util.DefaultClock;
5355
import com.google.android.gms.shadows.common.internal.ShadowPreconditions;
5456
import com.google.android.gms.tasks.Task;
5557
import com.google.android.gms.tasks.TaskCompletionSource;
@@ -104,6 +106,7 @@
104106
import org.junit.Test;
105107
import org.junit.runner.RunWith;
106108
import org.mockito.ArgumentCaptor;
109+
import org.mockito.ArgumentMatcher;
107110
import org.mockito.Mock;
108111
import org.mockito.MockitoAnnotations;
109112
import org.robolectric.RobolectricTestRunner;
@@ -200,6 +203,7 @@ public final class FirebaseRemoteConfigTest {
200203

201204
private final ScheduledExecutorService scheduledExecutorService =
202205
Executors.newSingleThreadScheduledExecutor();
206+
private final Clock clock = DefaultClock.getInstance();
203207

204208
@Before
205209
public void setUp() throws Exception {
@@ -351,7 +355,8 @@ public void onError(@NonNull FirebaseRemoteConfigException error) {
351355
mockActivatedCache,
352356
listeners,
353357
mockRetryListener,
354-
scheduledExecutorService);
358+
scheduledExecutorService,
359+
sharedPrefsClient);
355360
configAutoFetch.setIsInBackground(false);
356361
realtimeSharedPrefsClient =
357362
new ConfigSharedPrefsClient(
@@ -1551,6 +1556,34 @@ public void realtimeStreamListen_andUnableToParseMessage() throws Exception {
15511556
verify(mockInvalidMessageEventListener).onError(any(FirebaseRemoteConfigClientException.class));
15521557
}
15531558

1559+
@Test
1560+
public void realtime_updatesBackoffMetadataWithProvidedRetryInterval() throws Exception {
1561+
ConfigRealtimeHttpClient configRealtimeHttpClientSpy = spy(configRealtimeHttpClient);
1562+
when(mockHttpURLConnection.getResponseCode()).thenReturn(200);
1563+
int expectedRetryIntervalInSeconds = 240;
1564+
when(mockHttpURLConnection.getInputStream())
1565+
.thenReturn(
1566+
new ByteArrayInputStream(
1567+
String.format(
1568+
"{ \"latestTemplateVersionNumber\": 1, \"retryIntervalSeconds\": %d }",
1569+
expectedRetryIntervalInSeconds)
1570+
.getBytes(StandardCharsets.UTF_8)));
1571+
when(mockFetchHandler.getTemplateVersionNumber()).thenReturn(1L);
1572+
configAutoFetch.listenForNotifications();
1573+
1574+
ArgumentMatcher<Date> backoffEndTimeWithinTolerance =
1575+
argument -> {
1576+
Date currentTime = new Date(clock.currentTimeMillis());
1577+
long backoffDurationInMillis = expectedRetryIntervalInSeconds * 1000L;
1578+
Date expectedBackoffEndTime = new Date(currentTime.getTime() + backoffDurationInMillis);
1579+
return Math.abs(argument.getTime() - expectedBackoffEndTime.getTime())
1580+
<= TimeUnit.SECONDS.toSeconds(1);
1581+
};
1582+
1583+
verify(sharedPrefsClient, times(1))
1584+
.setRealtimeBackoffEndTime(argThat(backoffEndTimeWithinTolerance));
1585+
}
1586+
15541587
@Test
15551588
public void realtime_stream_listen_get_inputstream_fail() throws Exception {
15561589
InputStream inputStream = mock(InputStream.class);

0 commit comments

Comments
 (0)