-
Notifications
You must be signed in to change notification settings - Fork 627
Realtime Fetch ID #4328
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
Realtime Fetch ID #4328
Changes from 4 commits
6479e73
25e7e2b
2e7fe91
590f9ad
15389c8
fe13390
64798e9
83d5f4c
7d782ac
1595a03
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
|
@@ -85,6 +85,13 @@ public class ConfigFetchHandler { | |
*/ | ||
@VisibleForTesting static final String FIRST_OPEN_TIME_KEY = "_fot"; | ||
|
||
/** Custom Http header key to identify the fetch type. */ | ||
private static final String X_FIREBASE_RC_FETCH_TYPE = "X-Firebase-RC-Fetch-Type"; | ||
/** Fetch identifier for Realtime. */ | ||
private static final String REALTIME_FETCH_TYPE = "Realtime"; | ||
/** Fetch identifier for Base. */ | ||
private static final String BASE_FETCH_TYPE = "Base"; | ||
|
||
private final FirebaseInstallationsApi firebaseInstallations; | ||
private final Provider<AnalyticsConnector> analyticsConnector; | ||
|
||
|
@@ -156,13 +163,67 @@ public Task<FetchResponse> fetch() { | |
* updates, the {@link FetchResponse}'s configs will be {@code null}. | ||
*/ | ||
public Task<FetchResponse> fetch(long minimumFetchIntervalInSeconds) { | ||
|
||
// Make a copy to prevent any concurrency issues between Fetches. | ||
Map<String, String> copyOfCustomHttpHeaders = new HashMap<>(customHttpHeaders); | ||
copyOfCustomHttpHeaders.put(X_FIREBASE_RC_FETCH_TYPE, BASE_FETCH_TYPE + "/" + 1); | ||
|
||
return fetchedConfigsCache | ||
.get() | ||
.continueWithTask( | ||
executor, | ||
(cachedFetchConfigsTask) -> | ||
fetchIfCacheExpiredAndNotThrottled( | ||
cachedFetchConfigsTask, | ||
minimumFetchIntervalInSeconds, | ||
copyOfCustomHttpHeaders)); | ||
} | ||
|
||
/** | ||
* Starts fetching configs from the Firebase Remote Config server. | ||
* | ||
* <p>Guarantees consistency between memory and disk; fetched configs are saved to memory only | ||
* after they have been written to disk. | ||
* | ||
* <p>Fetches even if the read of the fetch cache fails (assumes there are no cached fetched | ||
* configs in that case). | ||
* | ||
* <p>If the fetch request could not be created or there was error connecting to the server, the | ||
* returned Task throws a {@link FirebaseRemoteConfigClientException}. | ||
* | ||
* <p>If the server responds with an error, the returned Task throws a {@link | ||
* FirebaseRemoteConfigServerException}. | ||
* | ||
* <p>If any of the following is true, then the returned Task throws a {@link | ||
* FirebaseRemoteConfigFetchThrottledException}: | ||
* | ||
* <ul> | ||
* <li>The backoff duration from a previous throttled exception has not expired, | ||
* <li>The backend responded with a throttled error, or | ||
* <li>The backend responded with unavailable errors for the last two fetch requests. | ||
* </ul> | ||
* | ||
* @return A {@link Task} representing a Realtime fetch call that returns a {@link FetchResponse} | ||
* with the configs fetched from the backend. If the backend was not called or the backend had | ||
* no updates, the {@link FetchResponse}'s configs will be {@code null}. | ||
*/ | ||
public Task<FetchResponse> realtimeFetch( | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It feels a bit off to me that half the header comes from calling a particular method ( Since the type and attempt are fetch details and realtime is just making use of them, passing both in (i.e. There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. I guess this makes sense. I updated it to create a new method that isn't so closely tied to Realtime. |
||
long minimumFetchIntervalInSeconds, int fetchAttemptNumber) { | ||
|
||
// Make a copy to prevent any concurrency issues between Fetches. | ||
Map<String, String> copyOfCustomHttpHeaders = new HashMap<>(customHttpHeaders); | ||
copyOfCustomHttpHeaders.put( | ||
X_FIREBASE_RC_FETCH_TYPE, REALTIME_FETCH_TYPE + "/" + fetchAttemptNumber); | ||
|
||
return fetchedConfigsCache | ||
.get() | ||
.continueWithTask( | ||
executor, | ||
(cachedFetchConfigsTask) -> | ||
fetchIfCacheExpiredAndNotThrottled( | ||
cachedFetchConfigsTask, minimumFetchIntervalInSeconds)); | ||
cachedFetchConfigsTask, | ||
minimumFetchIntervalInSeconds, | ||
copyOfCustomHttpHeaders)); | ||
} | ||
|
||
/** | ||
|
@@ -173,7 +234,9 @@ public Task<FetchResponse> fetch(long minimumFetchIntervalInSeconds) { | |
* fetch time and {@link BackoffMetadata} in {@link ConfigMetadataClient}. | ||
*/ | ||
private Task<FetchResponse> fetchIfCacheExpiredAndNotThrottled( | ||
Task<ConfigContainer> cachedFetchConfigsTask, long minimumFetchIntervalInSeconds) { | ||
Task<ConfigContainer> cachedFetchConfigsTask, | ||
long minimumFetchIntervalInSeconds, | ||
Map<String, String> customFetchHeaders) { | ||
Date currentTime = new Date(clock.currentTimeMillis()); | ||
if (cachedFetchConfigsTask.isSuccessful() | ||
&& areCachedFetchConfigsValid(minimumFetchIntervalInSeconds, currentTime)) { | ||
|
@@ -218,7 +281,7 @@ && areCachedFetchConfigsValid(minimumFetchIntervalInSeconds, currentTime)) { | |
String installationId = installationIdTask.getResult(); | ||
String installationToken = installationAuthTokenTask.getResult().getToken(); | ||
return fetchFromBackendAndCacheResponse( | ||
installationId, installationToken, currentTime); | ||
installationId, installationToken, currentTime, customFetchHeaders); | ||
}); | ||
} | ||
|
||
|
@@ -278,9 +341,13 @@ private String createThrottledMessage(long throttledDurationInMillis) { | |
* {@code fetchedConfigsCache}. | ||
*/ | ||
private Task<FetchResponse> fetchFromBackendAndCacheResponse( | ||
String installationId, String installationToken, Date fetchTime) { | ||
String installationId, | ||
String installationToken, | ||
Date fetchTime, | ||
Map<String, String> customFetchHeaders) { | ||
try { | ||
FetchResponse fetchResponse = fetchFromBackend(installationId, installationToken, fetchTime); | ||
FetchResponse fetchResponse = | ||
fetchFromBackend(installationId, installationToken, fetchTime, customFetchHeaders); | ||
if (fetchResponse.getStatus() != Status.BACKEND_UPDATES_FETCHED) { | ||
return Tasks.forResult(fetchResponse); | ||
} | ||
|
@@ -303,7 +370,10 @@ private Task<FetchResponse> fetchFromBackendAndCacheResponse( | |
*/ | ||
@WorkerThread | ||
private FetchResponse fetchFromBackend( | ||
String installationId, String installationToken, Date currentTime) | ||
String installationId, | ||
String installationToken, | ||
Date currentTime, | ||
Map<String, String> customFetchHeaders) | ||
throws FirebaseRemoteConfigException { | ||
try { | ||
HttpURLConnection urlConnection = frcBackendApiClient.createHttpURLConnection(); | ||
|
@@ -315,7 +385,7 @@ private FetchResponse fetchFromBackend( | |
installationToken, | ||
getUserProperties(), | ||
frcMetadata.getLastFetchETag(), | ||
customHttpHeaders, | ||
customFetchHeaders, | ||
getFirstOpenTime(), | ||
currentTime); | ||
|
||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This could use a comment since the math is a bit cryptic on its own. Maybe even worth just keeping a separate attempts counter?
Slightly related,
FETCH_RETRIES_ALLOWED
orINITIAL_FETCH_RETRIES
would be more descriptive as a variable name.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Updated.