Skip to content

Commit 445b7ae

Browse files
qdpham13danasilver
authored andcommitted
Realtime Fetch ID (#4328)
* Create overloaded fetch method specifically for realtime and add custom headers * Add comments for vars * Update header name * Change realtimeFetch name and params * Remove fetchHandler tests and move them to seperate PR * Remove unused vars and revert tests * Format tests * Add comments * Format files
1 parent f82d14c commit 445b7ae

File tree

3 files changed

+113
-17
lines changed

3 files changed

+113
-17
lines changed

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

Lines changed: 10 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -40,7 +40,7 @@
4040

4141
public class ConfigAutoFetch {
4242

43-
private static final int FETCH_RETRY = 3;
43+
private static final int MAXIMUM_FETCH_ATTEMPTS = 3;
4444
private static final String TEMPLATE_VERSION_KEY = "latestTemplateVersionNumber";
4545
private static final String REALTIME_DISABLED_KEY = "featureDisabled";
4646

@@ -155,7 +155,7 @@ private void handleNotifications(InputStream inputStream) throws IOException {
155155
long oldTemplateVersion = configFetchHandler.getTemplateVersionNumber();
156156
long targetTemplateVersion = jsonObject.getLong(TEMPLATE_VERSION_KEY);
157157
if (targetTemplateVersion > oldTemplateVersion) {
158-
autoFetch(FETCH_RETRY, targetTemplateVersion);
158+
autoFetch(MAXIMUM_FETCH_ATTEMPTS, targetTemplateVersion);
159159
}
160160
}
161161
} catch (JSONException ex) {
@@ -195,7 +195,13 @@ public void run() {
195195

196196
@VisibleForTesting
197197
public synchronized void fetchLatestConfig(int remainingAttempts, long targetVersion) {
198-
Task<ConfigFetchHandler.FetchResponse> fetchTask = configFetchHandler.fetch(0L);
198+
int currentAttempts = remainingAttempts - 1;
199+
200+
// fetchAttemptNumber is calculated by subtracting current attempts from the max number of
201+
// possible attempts.
202+
Task<ConfigFetchHandler.FetchResponse> fetchTask =
203+
configFetchHandler.fetchNowWithTypeAndAttemptNumber(
204+
ConfigFetchHandler.FetchType.REALTIME, MAXIMUM_FETCH_ATTEMPTS - currentAttempts);
199205
fetchTask.onSuccessTask(
200206
(fetchResponse) -> {
201207
long newTemplateVersion = 0;
@@ -214,7 +220,7 @@ public synchronized void fetchLatestConfig(int remainingAttempts, long targetVer
214220
"Fetched template version is the same as SDK's current version."
215221
+ " Retrying fetch.");
216222
// Continue fetching until template version number if greater then current.
217-
autoFetch(remainingAttempts - 1, targetVersion);
223+
autoFetch(currentAttempts, targetVersion);
218224
}
219225
return Tasks.forResult(null);
220226
});

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

Lines changed: 87 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -85,6 +85,9 @@ public class ConfigFetchHandler {
8585
*/
8686
@VisibleForTesting static final String FIRST_OPEN_TIME_KEY = "_fot";
8787

88+
/** Custom Http header key to identify the fetch type. */
89+
private static final String X_FIREBASE_RC_FETCH_TYPE = "X-Firebase-RC-Fetch-Type";
90+
8891
private final FirebaseInstallationsApi firebaseInstallations;
8992
private final Provider<AnalyticsConnector> analyticsConnector;
9093

@@ -156,13 +159,66 @@ public Task<FetchResponse> fetch() {
156159
* updates, the {@link FetchResponse}'s configs will be {@code null}.
157160
*/
158161
public Task<FetchResponse> fetch(long minimumFetchIntervalInSeconds) {
162+
163+
// Make a copy to prevent any concurrency issues between Fetches.
164+
Map<String, String> copyOfCustomHttpHeaders = new HashMap<>(customHttpHeaders);
165+
copyOfCustomHttpHeaders.put(X_FIREBASE_RC_FETCH_TYPE, FetchType.BASE.getValue() + "/" + 1);
166+
159167
return fetchedConfigsCache
160168
.get()
161169
.continueWithTask(
162170
executor,
163171
(cachedFetchConfigsTask) ->
164172
fetchIfCacheExpiredAndNotThrottled(
165-
cachedFetchConfigsTask, minimumFetchIntervalInSeconds));
173+
cachedFetchConfigsTask,
174+
minimumFetchIntervalInSeconds,
175+
copyOfCustomHttpHeaders));
176+
}
177+
178+
/**
179+
* Starts fetching configs from the Firebase Remote Config server.
180+
*
181+
* <p>Guarantees consistency between memory and disk; fetched configs are saved to memory only
182+
* after they have been written to disk.
183+
*
184+
* <p>Fetches even if the read of the fetch cache fails (assumes there are no cached fetched
185+
* configs in that case).
186+
*
187+
* <p>If the fetch request could not be created or there was error connecting to the server, the
188+
* returned Task throws a {@link FirebaseRemoteConfigClientException}.
189+
*
190+
* <p>If the server responds with an error, the returned Task throws a {@link
191+
* FirebaseRemoteConfigServerException}.
192+
*
193+
* <p>If any of the following is true, then the returned Task throws a {@link
194+
* FirebaseRemoteConfigFetchThrottledException}:
195+
*
196+
* <ul>
197+
* <li>The backoff duration from a previous throttled exception has not expired,
198+
* <li>The backend responded with a throttled error, or
199+
* <li>The backend responded with unavailable errors for the last two fetch requests.
200+
* </ul>
201+
*
202+
* @param {@link FetchType} and fetchAttemptNumber help detail what started the fetch call.
203+
* @return A {@link Task} representing an immediate fetch call that returns a {@link
204+
* FetchResponse} with the configs fetched from the backend. If the backend was not called or
205+
* the backend had no updates, the {@link FetchResponse}'s configs will be {@code null}.
206+
*/
207+
public Task<FetchResponse> fetchNowWithTypeAndAttemptNumber(
208+
FetchType fetchType, int fetchAttemptNumber) {
209+
210+
// Make a copy to prevent any concurrency issues between Fetches.
211+
Map<String, String> copyOfCustomHttpHeaders = new HashMap<>(customHttpHeaders);
212+
copyOfCustomHttpHeaders.put(
213+
X_FIREBASE_RC_FETCH_TYPE, fetchType.getValue() + "/" + fetchAttemptNumber);
214+
215+
return fetchedConfigsCache
216+
.get()
217+
.continueWithTask(
218+
executor,
219+
(cachedFetchConfigsTask) ->
220+
fetchIfCacheExpiredAndNotThrottled(
221+
cachedFetchConfigsTask, 0, copyOfCustomHttpHeaders));
166222
}
167223

168224
/**
@@ -173,7 +229,9 @@ public Task<FetchResponse> fetch(long minimumFetchIntervalInSeconds) {
173229
* fetch time and {@link BackoffMetadata} in {@link ConfigMetadataClient}.
174230
*/
175231
private Task<FetchResponse> fetchIfCacheExpiredAndNotThrottled(
176-
Task<ConfigContainer> cachedFetchConfigsTask, long minimumFetchIntervalInSeconds) {
232+
Task<ConfigContainer> cachedFetchConfigsTask,
233+
long minimumFetchIntervalInSeconds,
234+
Map<String, String> customFetchHeaders) {
177235
Date currentTime = new Date(clock.currentTimeMillis());
178236
if (cachedFetchConfigsTask.isSuccessful()
179237
&& areCachedFetchConfigsValid(minimumFetchIntervalInSeconds, currentTime)) {
@@ -218,7 +276,7 @@ && areCachedFetchConfigsValid(minimumFetchIntervalInSeconds, currentTime)) {
218276
String installationId = installationIdTask.getResult();
219277
String installationToken = installationAuthTokenTask.getResult().getToken();
220278
return fetchFromBackendAndCacheResponse(
221-
installationId, installationToken, currentTime);
279+
installationId, installationToken, currentTime, customFetchHeaders);
222280
});
223281
}
224282

@@ -278,9 +336,13 @@ private String createThrottledMessage(long throttledDurationInMillis) {
278336
* {@code fetchedConfigsCache}.
279337
*/
280338
private Task<FetchResponse> fetchFromBackendAndCacheResponse(
281-
String installationId, String installationToken, Date fetchTime) {
339+
String installationId,
340+
String installationToken,
341+
Date fetchTime,
342+
Map<String, String> customFetchHeaders) {
282343
try {
283-
FetchResponse fetchResponse = fetchFromBackend(installationId, installationToken, fetchTime);
344+
FetchResponse fetchResponse =
345+
fetchFromBackend(installationId, installationToken, fetchTime, customFetchHeaders);
284346
if (fetchResponse.getStatus() != Status.BACKEND_UPDATES_FETCHED) {
285347
return Tasks.forResult(fetchResponse);
286348
}
@@ -303,7 +365,10 @@ private Task<FetchResponse> fetchFromBackendAndCacheResponse(
303365
*/
304366
@WorkerThread
305367
private FetchResponse fetchFromBackend(
306-
String installationId, String installationToken, Date currentTime)
368+
String installationId,
369+
String installationToken,
370+
Date currentTime,
371+
Map<String, String> customFetchHeaders)
307372
throws FirebaseRemoteConfigException {
308373
try {
309374
HttpURLConnection urlConnection = frcBackendApiClient.createHttpURLConnection();
@@ -315,7 +380,7 @@ private FetchResponse fetchFromBackend(
315380
installationToken,
316381
getUserProperties(),
317382
frcMetadata.getLastFetchETag(),
318-
customHttpHeaders,
383+
customFetchHeaders,
319384
getFirstOpenTime(),
320385
currentTime);
321386

@@ -626,4 +691,19 @@ public ConfigContainer getFetchedConfigs() {
626691
int LOCAL_STORAGE_USED = 2;
627692
}
628693
}
694+
695+
public enum FetchType {
696+
BASE("Base"),
697+
REALTIME("Realtime");
698+
699+
private final String value;
700+
701+
FetchType(String value) {
702+
this.value = value;
703+
}
704+
705+
String getValue() {
706+
return value;
707+
}
708+
}
629709
}

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

Lines changed: 16 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1156,7 +1156,9 @@ public void realtime_stream_listen_and_retry_success() throws Exception {
11561156
new ByteArrayInputStream(
11571157
"{ \"latestTemplateVersionNumber\": 1 }".getBytes(StandardCharsets.UTF_8)));
11581158
when(mockFetchHandler.getTemplateVersionNumber()).thenReturn(1L);
1159-
when(mockFetchHandler.fetch(0)).thenReturn(Tasks.forResult(realtimeFetchedContainerResponse));
1159+
when(mockFetchHandler.fetchNowWithTypeAndAttemptNumber(
1160+
ConfigFetchHandler.FetchType.REALTIME, 1))
1161+
.thenReturn(Tasks.forResult(realtimeFetchedContainerResponse));
11601162
configAutoFetch.listenForNotifications();
11611163

11621164
verify(mockRetryListener).onEvent();
@@ -1245,7 +1247,9 @@ public void realtime_stream_listen_and_failsafe_disabled() throws Exception {
12451247
"{ \"featureDisabled\": false, \"latestTemplateVersionNumber\": 2 }"
12461248
.getBytes(StandardCharsets.UTF_8)));
12471249
when(mockFetchHandler.getTemplateVersionNumber()).thenReturn(1L);
1248-
when(mockFetchHandler.fetch(0)).thenReturn(Tasks.forResult(realtimeFetchedContainerResponse));
1250+
when(mockFetchHandler.fetchNowWithTypeAndAttemptNumber(
1251+
ConfigFetchHandler.FetchType.REALTIME, 1))
1252+
.thenReturn(Tasks.forResult(realtimeFetchedContainerResponse));
12491253
configAutoFetch.listenForNotifications();
12501254

12511255
verify(mockUnavailableEventListener, never())
@@ -1258,7 +1262,9 @@ public void realtime_stream_listen_get_inputstream_fail() throws Exception {
12581262
when(mockHttpURLConnection.getResponseCode()).thenReturn(200);
12591263
when(mockHttpURLConnection.getInputStream()).thenThrow(IOException.class);
12601264
when(mockFetchHandler.getTemplateVersionNumber()).thenReturn(1L);
1261-
when(mockFetchHandler.fetch(0)).thenReturn(Tasks.forResult(realtimeFetchedContainerResponse));
1265+
when(mockFetchHandler.fetchNowWithTypeAndAttemptNumber(
1266+
ConfigFetchHandler.FetchType.REALTIME, 1))
1267+
.thenReturn(Tasks.forResult(realtimeFetchedContainerResponse));
12621268
configAutoFetch.listenForNotifications();
12631269

12641270
verify(mockInvalidMessageEventListener).onError(any(FirebaseRemoteConfigClientException.class));
@@ -1267,16 +1273,20 @@ public void realtime_stream_listen_get_inputstream_fail() throws Exception {
12671273
@Test
12681274
public void realtime_stream_autofetch_success() {
12691275
when(mockFetchHandler.getTemplateVersionNumber()).thenReturn(1L);
1270-
when(mockFetchHandler.fetch(0)).thenReturn(Tasks.forResult(realtimeFetchedContainerResponse));
1271-
configAutoFetch.fetchLatestConfig(1, 1);
1276+
when(mockFetchHandler.fetchNowWithTypeAndAttemptNumber(
1277+
ConfigFetchHandler.FetchType.REALTIME, 1))
1278+
.thenReturn(Tasks.forResult(realtimeFetchedContainerResponse));
1279+
configAutoFetch.fetchLatestConfig(3, 1);
12721280

12731281
verify(mockOnEventListener).onEvent();
12741282
}
12751283

12761284
@Test
12771285
public void realtime_stream_autofetch_failure() {
12781286
when(mockFetchHandler.getTemplateVersionNumber()).thenReturn(1L);
1279-
when(mockFetchHandler.fetch(0)).thenReturn(Tasks.forResult(realtimeFetchedContainerResponse));
1287+
when(mockFetchHandler.fetchNowWithTypeAndAttemptNumber(
1288+
ConfigFetchHandler.FetchType.REALTIME, 3))
1289+
.thenReturn(Tasks.forResult(realtimeFetchedContainerResponse));
12801290
configAutoFetch.fetchLatestConfig(1, 1000);
12811291

12821292
verify(mockNotFetchedEventListener).onError(any(FirebaseRemoteConfigServerException.class));

0 commit comments

Comments
 (0)