Skip to content

Refactor RCM fetch random delay to not depend on FirebaseApp initialization. #7214

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 2 commits into from
Aug 5, 2025
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: 1 addition & 1 deletion firebase-perf/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
# Unreleased

* [fixed] Fixed an ANR on app launch. [#4831]

# 22.0.0
* [changed] **Breaking Change**: Updated minSdkVersion to API level 23 or higher.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -23,8 +23,6 @@
import androidx.annotation.Keep;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.firebase.FirebaseApp;
import com.google.firebase.StartupTime;
import com.google.firebase.inject.Provider;
import com.google.firebase.perf.logging.AndroidLogger;
import com.google.firebase.perf.util.Optional;
Expand Down Expand Up @@ -54,15 +52,14 @@ public class RemoteConfigManager {
private static final long TIME_AFTER_WHICH_A_FETCH_IS_CONSIDERED_STALE_MS =
TimeUnit.HOURS.toMillis(12);
private static final long FETCH_NEVER_HAPPENED_TIMESTAMP_MS = 0;
private static final long MIN_APP_START_CONFIG_FETCH_DELAY_MS = 5000;
private static final int RANDOM_APP_START_CONFIG_FETCH_DELAY_MS = 25000;
private static final long MIN_CONFIG_FETCH_DELAY_MS = 5000;
private static final int RANDOM_CONFIG_FETCH_DELAY_MS = 25000;
private final long rcmInitTimestamp = getCurrentSystemTimeMillis();

private final DeviceCacheManager cache;
private final ConcurrentHashMap<String, FirebaseRemoteConfigValue> allRcConfigMap;
private final Executor executor;
private final long appStartTimeInMs;
private final long appStartConfigFetchDelayInMs;

private final long remoteConfigFetchDelayInMs;
private long firebaseRemoteConfigLastFetchTimestampMs = FETCH_NEVER_HAPPENED_TIMESTAMP_MS;

@Nullable private Provider<RemoteConfigComponent> firebaseRemoteConfigProvider;
Expand All @@ -80,44 +77,23 @@ private RemoteConfigManager() {
TimeUnit.SECONDS,
new LinkedBlockingQueue<Runnable>()),
/* firebaseRemoteConfig= */ null, // set once FirebaseRemoteConfig is initialized
MIN_APP_START_CONFIG_FETCH_DELAY_MS
+ new Random().nextInt(RANDOM_APP_START_CONFIG_FETCH_DELAY_MS),
getInitialStartupMillis());
}

@VisibleForTesting
@SuppressWarnings("FirebaseUseExplicitDependencies")
static long getInitialStartupMillis() {
StartupTime startupTime = null;
try {
startupTime = FirebaseApp.getInstance().get(StartupTime.class);
} catch (IllegalStateException ex) {
// This can happen if you start a trace before Firebase is init
logger.debug("Unable to get StartupTime instance.");
}
if (startupTime != null) {
return startupTime.getEpochMillis();
} else {
return System.currentTimeMillis();
}
MIN_CONFIG_FETCH_DELAY_MS + new Random().nextInt(RANDOM_CONFIG_FETCH_DELAY_MS));
}

@VisibleForTesting
RemoteConfigManager(
DeviceCacheManager cache,
Executor executor,
FirebaseRemoteConfig firebaseRemoteConfig,
long appStartConfigFetchDelayInMs,
long appStartTimeInMs) {
long remoteConfigFetchDelayInMs) {
this.cache = cache;
this.executor = executor;
this.firebaseRemoteConfig = firebaseRemoteConfig;
this.allRcConfigMap =
firebaseRemoteConfig == null
? new ConcurrentHashMap<>()
: new ConcurrentHashMap<>(firebaseRemoteConfig.getAll());
this.appStartTimeInMs = appStartTimeInMs;
this.appStartConfigFetchDelayInMs = appStartConfigFetchDelayInMs;
this.remoteConfigFetchDelayInMs = remoteConfigFetchDelayInMs;
}

/** Gets the singleton instance. */
Expand Down Expand Up @@ -329,7 +305,7 @@ public boolean isLastFetchFailed() {
*
* <ol>
* <li>Firebase Remote Config is available,
* <li>Time-since-app-start has passed a randomized delay-time (b/187985523), and
* <li>Time has passed a randomized delay-time (b/187985523), and
* <li>At least 12 hours have passed since the previous fetch.
* </ol>
*/
Expand Down Expand Up @@ -408,17 +384,17 @@ public boolean isFirebaseRemoteConfigAvailable() {
/** Returns true if a RC fetch should be made, false otherwise. */
private boolean shouldFetchAndActivateRemoteConfigValues() {
long currentTimeInMs = getCurrentSystemTimeMillis();
return hasAppStartConfigFetchDelayElapsed(currentTimeInMs)
return hasRemoteConfigFetchDelayElapsed(currentTimeInMs)
&& hasLastFetchBecomeStale(currentTimeInMs);
}

/**
* Delay fetch by some random time since app start. This is to prevent b/187985523.
* Delay fetch by some random time. This is to prevent b/187985523.
*
* @return true if the random delay has elapsed, false otherwise
*/
private boolean hasAppStartConfigFetchDelayElapsed(long currentTimeInMs) {
return (currentTimeInMs - appStartTimeInMs) >= appStartConfigFetchDelayInMs;
private boolean hasRemoteConfigFetchDelayElapsed(long currentTimeInMs) {
return (currentTimeInMs - rcmInitTimestamp) >= remoteConfigFetchDelayInMs;
}

// We want to fetch once when the app starts and every 12 hours after that.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -835,20 +835,20 @@ public void isLastFetchFailed_frcIsNonNullAndStatusOtherThanFailedOrThrottled_re
}

@Test
public void triggerRemoteConfigFetchIfNecessary_doesNotFetchBeforeAppStartRandomDelay() {
long appStartConfigFetchDelay = 5000;
public void triggerRemoteConfigFetchIfNecessary_doesNotFetchBeforeRandomDelay() {
long remoteConfigFetchDelay = 5000;
RemoteConfigManager remoteConfigManagerPartialMock =
spy(
setupTestRemoteConfigManager(
createFakeTaskThatDoesNothing(),
true,
createDefaultRcConfigMap(),
appStartConfigFetchDelay));
remoteConfigFetchDelay));

// Simulate time fast forward to some time before fetch time is up
long appStartTimeInMs = System.currentTimeMillis();
long approxRcmInitTimestampMs = System.currentTimeMillis();
when(remoteConfigManagerPartialMock.getCurrentSystemTimeMillis())
.thenReturn(appStartTimeInMs + appStartConfigFetchDelay - 2000);
.thenReturn(approxRcmInitTimestampMs + remoteConfigFetchDelay - 2000);

simulateFirebaseRemoteConfigLastFetchStatus(
FirebaseRemoteConfig.LAST_FETCH_STATUS_NO_FETCH_YET);
Expand All @@ -861,20 +861,20 @@ public void triggerRemoteConfigFetchIfNecessary_doesNotFetchBeforeAppStartRandom
}

@Test
public void triggerRemoteConfigFetchIfNecessary_fetchesAfterAppStartRandomDelay() {
long appStartConfigFetchDelay = 5000;
public void triggerRemoteConfigFetchIfNecessary_fetchesAfterRandomDelay() {
long remoteConfigFetchDelay = 5000;
RemoteConfigManager remoteConfigManagerPartialMock =
spy(
setupTestRemoteConfigManager(
createFakeTaskThatDoesNothing(),
true,
createDefaultRcConfigMap(),
appStartConfigFetchDelay));
remoteConfigFetchDelay));

// Simulate time fast forward to 2s after fetch delay time is up
long appStartTimeInMs = System.currentTimeMillis();
long approxRcmInitTimestampInMs = System.currentTimeMillis();
when(remoteConfigManagerPartialMock.getCurrentSystemTimeMillis())
.thenReturn(appStartTimeInMs + appStartConfigFetchDelay + 2000);
.thenReturn(approxRcmInitTimestampInMs + remoteConfigFetchDelay + 2000);

simulateFirebaseRemoteConfigLastFetchStatus(
FirebaseRemoteConfig.LAST_FETCH_STATUS_NO_FETCH_YET);
Expand Down Expand Up @@ -918,24 +918,16 @@ private RemoteConfigManager setupTestRemoteConfigManager(
Task<Boolean> fakeTask,
boolean initializeFrc,
Map<String, FirebaseRemoteConfigValue> configs,
long appStartConfigFetchDelayInMs) {
long remoteConfigFetchDelayInMs) {
simulateFirebaseRemoteConfigLastFetchStatus(FirebaseRemoteConfig.LAST_FETCH_STATUS_SUCCESS);
when(mockFirebaseRemoteConfig.fetchAndActivate()).thenReturn(fakeTask);
when(mockFirebaseRemoteConfig.getAll()).thenReturn(configs);
if (initializeFrc) {
return new RemoteConfigManager(
cacheManager,
fakeExecutor,
mockFirebaseRemoteConfig,
appStartConfigFetchDelayInMs,
RemoteConfigManager.getInitialStartupMillis());
cacheManager, fakeExecutor, mockFirebaseRemoteConfig, remoteConfigFetchDelayInMs);
} else {
return new RemoteConfigManager(
cacheManager,
fakeExecutor,
/* firebaseRemoteConfig= */ null,
appStartConfigFetchDelayInMs,
RemoteConfigManager.getInitialStartupMillis());
cacheManager, fakeExecutor, /* firebaseRemoteConfig= */ null, remoteConfigFetchDelayInMs);
}
}

Expand Down