Skip to content

Commit 191700a

Browse files
Fireperf: create a random delay before initiating a remote config fetch. (#2968)
* Implement random delay before RC fetch from app start * renames and comments as per code review * googleJavaFormat * code review changes * revert some code review changes * use FirebasePerfProvider.getAppStartTime
1 parent 21a9f91 commit 191700a

File tree

2 files changed

+111
-8
lines changed

2 files changed

+111
-8
lines changed

firebase-perf/src/main/java/com/google/firebase/perf/config/RemoteConfigManager.java

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -22,11 +22,13 @@
2222
import com.google.android.gms.common.util.VisibleForTesting;
2323
import com.google.firebase.inject.Provider;
2424
import com.google.firebase.perf.logging.AndroidLogger;
25+
import com.google.firebase.perf.provider.FirebasePerfProvider;
2526
import com.google.firebase.perf.util.Optional;
2627
import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
2728
import com.google.firebase.remoteconfig.FirebaseRemoteConfigValue;
2829
import com.google.firebase.remoteconfig.RemoteConfigComponent;
2930
import java.util.Map;
31+
import java.util.Random;
3032
import java.util.concurrent.ConcurrentHashMap;
3133
import java.util.concurrent.Executor;
3234
import java.util.concurrent.LinkedBlockingQueue;
@@ -48,9 +50,13 @@ public class RemoteConfigManager {
4850
private static final long TIME_AFTER_WHICH_A_FETCH_IS_CONSIDERED_STALE_MS =
4951
TimeUnit.HOURS.toMillis(12);
5052
private static final long FETCH_NEVER_HAPPENED_TIMESTAMP_MS = 0;
53+
private static final long MIN_APP_START_CONFIG_FETCH_DELAY_MS = 5000;
54+
private static final int RANDOM_APP_START_CONFIG_FETCH_DELAY_MS = 25000;
5155

5256
private final ConcurrentHashMap<String, FirebaseRemoteConfigValue> allRcConfigMap;
5357
private final Executor executor;
58+
private final long appStartTimeInMs;
59+
private final long appStartConfigFetchDelayInMs;
5460

5561
private long firebaseRemoteConfigLastFetchTimestampMs = FETCH_NEVER_HAPPENED_TIMESTAMP_MS;
5662

@@ -69,14 +75,28 @@ private RemoteConfigManager() {
6975
);
7076
}
7177

72-
@VisibleForTesting
7378
RemoteConfigManager(Executor executor, FirebaseRemoteConfig firebaseRemoteConfig) {
79+
this(
80+
executor,
81+
firebaseRemoteConfig,
82+
MIN_APP_START_CONFIG_FETCH_DELAY_MS
83+
+ new Random().nextInt(RANDOM_APP_START_CONFIG_FETCH_DELAY_MS));
84+
}
85+
86+
@VisibleForTesting
87+
RemoteConfigManager(
88+
Executor executor,
89+
FirebaseRemoteConfig firebaseRemoteConfig,
90+
long appStartConfigFetchDelayInMs) {
7491
this.executor = executor;
7592
this.firebaseRemoteConfig = firebaseRemoteConfig;
7693
this.allRcConfigMap =
7794
firebaseRemoteConfig == null
7895
? new ConcurrentHashMap<>()
7996
: new ConcurrentHashMap<>(firebaseRemoteConfig.getAll());
97+
this.appStartTimeInMs =
98+
TimeUnit.MICROSECONDS.toMillis(FirebasePerfProvider.getAppStartTime().getMicros());
99+
this.appStartConfigFetchDelayInMs = appStartConfigFetchDelayInMs;
80100
}
81101

82102
/** Gets the singleton instance. */
@@ -282,8 +302,13 @@ public boolean isLastFetchFailed() {
282302
}
283303

284304
/**
285-
* Triggers a fetch and async activate from Firebase Remote Config if Firebase Remote Config is
286-
* available, and at least 12 hours have passed since the previous fetch.
305+
* Triggers a fetch and async activate from Firebase Remote Config if:
306+
*
307+
* <ol>
308+
* <li>Firebase Remote Config is available,
309+
* <li>Time-since-app-start has passed a randomized delay-time (b/187985523), and
310+
* <li>At least 12 hours have passed since the previous fetch.
311+
* </ol>
287312
*/
288313
private void triggerRemoteConfigFetchIfNecessary() {
289314
if (!isFirebaseRemoteConfigAvailable()) {
@@ -339,15 +364,31 @@ public boolean isFirebaseRemoteConfigAvailable() {
339364
return firebaseRemoteConfig != null;
340365
}
341366

367+
/** Returns true if a RC fetch should be made, false otherwise. */
368+
private boolean shouldFetchAndActivateRemoteConfigValues() {
369+
long currentTimeInMs = getCurrentSystemTimeMillis();
370+
return hasAppStartConfigFetchDelayElapsed(currentTimeInMs)
371+
&& hasLastFetchBecomeStale(currentTimeInMs);
372+
}
373+
374+
/**
375+
* Delay fetch by some random time since app start. This is to prevent b/187985523.
376+
*
377+
* @return true if the random delay has elapsed, false otherwise
378+
*/
379+
private boolean hasAppStartConfigFetchDelayElapsed(long currentTimeInMs) {
380+
return (currentTimeInMs - appStartTimeInMs) >= appStartConfigFetchDelayInMs;
381+
}
382+
342383
// We want to fetch once when the app starts and every 12 hours after that.
343384
// The reason we maintain our own timestamps and do not use FRC's is because FRC only updates
344385
// the last successful fetch timestamp AFTER successfully fetching - which might mean that
345386
// we could potentially fire off multiple fetch requests. This protects against that because
346387
// we update the timestamp before a successful fetch and reset it back if the fetch was
347388
// unsuccessful, making sure that a fetch is triggered again.
348389
// TODO(b/132369190): This shouldn't be needed once the feature is implemented in FRC.
349-
private boolean shouldFetchAndActivateRemoteConfigValues() {
350-
return (getCurrentSystemTimeMillis() - firebaseRemoteConfigLastFetchTimestampMs)
390+
private boolean hasLastFetchBecomeStale(long currentTimeInMs) {
391+
return (currentTimeInMs - firebaseRemoteConfigLastFetchTimestampMs)
351392
> TIME_AFTER_WHICH_A_FETCH_IS_CONSIDERED_STALE_MS;
352393
}
353394

firebase-perf/src/test/java/com/google/firebase/perf/config/RemoteConfigManagerTest.java

Lines changed: 65 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@
2727
import com.google.android.gms.tasks.TaskCompletionSource;
2828
import com.google.firebase.inject.Provider;
2929
import com.google.firebase.perf.FirebasePerformanceTestBase;
30+
import com.google.firebase.perf.provider.FirebasePerfProvider;
3031
import com.google.firebase.remoteconfig.FirebaseRemoteConfig;
3132
import com.google.firebase.remoteconfig.FirebaseRemoteConfigInfo;
3233
import com.google.firebase.remoteconfig.FirebaseRemoteConfigSettings;
@@ -796,6 +797,57 @@ public void isLastFetchFailed_frcIsNonNullAndStatusOtherThanFailed_returnsFalse(
796797
assertThat(testRemoteConfigManager.isLastFetchFailed()).isFalse();
797798
}
798799

800+
@Test
801+
public void triggerRemoteConfigFetchIfNecessary_doesNotFetchBeforeAppStartRandomDelay() {
802+
long appStartConfigFetchDelay = 5000;
803+
RemoteConfigManager remoteConfigManagerPartialMock =
804+
spy(
805+
setupTestRemoteConfigManager(
806+
createFakeTaskThatDoesNothing(),
807+
true,
808+
createDefaultRcConfigMap(),
809+
appStartConfigFetchDelay));
810+
811+
// Simulate time fast forward to some time before fetch time is up
812+
long appStartTimeInMs =
813+
TimeUnit.MICROSECONDS.toMillis(FirebasePerfProvider.getAppStartTime().getMicros());
814+
when(remoteConfigManagerPartialMock.getCurrentSystemTimeMillis())
815+
.thenReturn(appStartTimeInMs + appStartConfigFetchDelay - 2000);
816+
817+
simulateFirebaseRemoteConfigLastFetchStatus(
818+
FirebaseRemoteConfig.LAST_FETCH_STATUS_NO_FETCH_YET);
819+
remoteConfigManagerPartialMock.getRemoteConfigValueOrDefault("some_key", 5L);
820+
remoteConfigManagerPartialMock.getRemoteConfigValueOrDefault("some_other_key", 5.0f);
821+
remoteConfigManagerPartialMock.getRemoteConfigValueOrDefault("some_other_key_2", true);
822+
remoteConfigManagerPartialMock.getRemoteConfigValueOrDefault("some_other_key_3", "1.0.0");
823+
824+
verify(mockFirebaseRemoteConfig, times(0)).fetchAndActivate();
825+
}
826+
827+
@Test
828+
public void triggerRemoteConfigFetchIfNecessary_fetchesAfterAppStartRandomDelay() {
829+
long appStartConfigFetchDelay = 5000;
830+
RemoteConfigManager remoteConfigManagerPartialMock =
831+
spy(
832+
setupTestRemoteConfigManager(
833+
createFakeTaskThatDoesNothing(),
834+
true,
835+
createDefaultRcConfigMap(),
836+
appStartConfigFetchDelay));
837+
838+
// Simulate time fast forward to 2s after fetch delay time is up
839+
long appStartTimeInMs =
840+
TimeUnit.MICROSECONDS.toMillis(FirebasePerfProvider.getAppStartTime().getMicros());
841+
when(remoteConfigManagerPartialMock.getCurrentSystemTimeMillis())
842+
.thenReturn(appStartTimeInMs + appStartConfigFetchDelay + 2000);
843+
844+
simulateFirebaseRemoteConfigLastFetchStatus(
845+
FirebaseRemoteConfig.LAST_FETCH_STATUS_NO_FETCH_YET);
846+
remoteConfigManagerPartialMock.getRemoteConfigValueOrDefault("some_key", 5L);
847+
848+
verify(mockFirebaseRemoteConfig, times(1)).fetchAndActivate();
849+
}
850+
799851
private void simulateFirebaseRemoteConfigLastFetchStatus(int lastFetchStatus) {
800852
when(mockFirebaseRemoteConfig.getInfo())
801853
.thenReturn(
@@ -830,17 +882,27 @@ private Task<Boolean> createFakeTaskThatDoesNothing() {
830882
private RemoteConfigManager setupTestRemoteConfigManager(
831883
Task<Boolean> fakeTask,
832884
boolean initializeFrc,
833-
Map<String, FirebaseRemoteConfigValue> configs) {
885+
Map<String, FirebaseRemoteConfigValue> configs,
886+
long appStartConfigFetchDelayInMs) {
834887
simulateFirebaseRemoteConfigLastFetchStatus(FirebaseRemoteConfig.LAST_FETCH_STATUS_SUCCESS);
835888
when(mockFirebaseRemoteConfig.fetchAndActivate()).thenReturn(fakeTask);
836889
when(mockFirebaseRemoteConfig.getAll()).thenReturn(configs);
837890
if (initializeFrc) {
838-
return new RemoteConfigManager(fakeExecutor, mockFirebaseRemoteConfig);
891+
return new RemoteConfigManager(
892+
fakeExecutor, mockFirebaseRemoteConfig, appStartConfigFetchDelayInMs);
839893
} else {
840-
return new RemoteConfigManager(fakeExecutor, /* firebaseRemoteConfig= */ null);
894+
return new RemoteConfigManager(
895+
fakeExecutor, /* firebaseRemoteConfig= */ null, appStartConfigFetchDelayInMs);
841896
}
842897
}
843898

899+
private RemoteConfigManager setupTestRemoteConfigManager(
900+
Task<Boolean> fakeTask,
901+
boolean initializeFrc,
902+
Map<String, FirebaseRemoteConfigValue> configs) {
903+
return setupTestRemoteConfigManager(fakeTask, initializeFrc, configs, 0);
904+
}
905+
844906
/**
845907
* Creates and returns a test instance of RemoteConfigManager that has {@link
846908
* RemoteConfigManagerTest#fakeExecutor} and {@link

0 commit comments

Comments
 (0)