Skip to content

FIS error handling #911

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 30 commits into from
Oct 16, 2019
Merged
Show file tree
Hide file tree
Changes from 29 commits
Commits
Show all changes
30 commits
Select commit Hold shift + click to select a range
49708d2
Simplifying FirebaseInstallations class by adding listeners.
ankitaj224 Sep 25, 2019
a987647
Splitting multiple classes/interfaces into separate files.
ankitaj224 Sep 25, 2019
1d9b1ca
Addressing ciaran's comments to return same token if multiple getAuth…
ankitaj224 Sep 26, 2019
28d0d85
Addressing ciaran's comments to use a boolean to refresh auth token
ankitaj224 Sep 27, 2019
126e372
Cleaning up instrumented test file.
ankitaj224 Sep 27, 2019
c39cdb8
Ciaran's comments on synchronization and runnable.
ankitaj224 Sep 29, 2019
c21a59e
Ciaran's comments on synchronization and runnable.
ankitaj224 Sep 29, 2019
7a46ab0
Merge branch 'listeners' of github.com:firebase/firebase-android-sdk …
ankitaj224 Sep 29, 2019
314b3bb
Fixing precheck failures.
ankitaj224 Sep 30, 2019
715d427
Addressing Ciaran's comment on mock utils.
ankitaj224 Sep 30, 2019
bb322d0
Cleaning doRegistration method.
ankitaj224 Oct 1, 2019
1fd193b
Fixing nit comments.
ankitaj224 Oct 8, 2019
e2d3631
Addressing ciaran's comment on satisfying immediate getId response.
ankitaj224 Oct 8, 2019
e68e599
Fixing FISClient to correctly parse expiration timestamp. (#848)
ankitaj224 Oct 9, 2019
58c8efd
Updating getAuthToken to return creation timestamp (#884)
ankitaj224 Oct 9, 2019
56d1eab
Propagating the exceptions to the clients. (#856)
ankitaj224 Oct 10, 2019
c8367dc
Implemneting retry for FIS Client.
ankitaj224 Oct 10, 2019
72e335d
Merge branch 'listeners' of github.com:firebase/firebase-android-sdk …
ankitaj224 Oct 10, 2019
560fba1
Addressing ciaran's comments.
ankitaj224 Oct 10, 2019
3ac3da2
Merge branch 'fis_sdk' of github.com:firebase/firebase-android-sdk in…
ankitaj224 Oct 10, 2019
d9e5c22
Merge conflicts.
ankitaj224 Oct 10, 2019
f57137c
Nit fixes
ankitaj224 Oct 10, 2019
1d51edf
Extract FID from FIS createInstallation response (#888)
ankitaj224 Oct 14, 2019
0c0b9c2
Merge branch 'listeners' of github.com:firebase/firebase-android-sdk …
ankitaj224 Oct 14, 2019
0407614
Fixing verify format issue.
ankitaj224 Oct 14, 2019
2e7b881
1. Mark PersistedInstallationEntry status UNREGISTERED incase of 500 …
ankitaj224 Oct 14, 2019
615bdf4
Merge branch 'fis_sdk' of github.com:firebase/firebase-android-sdk in…
ankitaj224 Oct 14, 2019
dfa85e7
Addressing Ciaran & Rayo's comments.
ankitaj224 Oct 14, 2019
97b616c
Acceptiong Rayo's suggestion on code improvement.
ankitaj224 Oct 15, 2019
31d441a
Introducing responseCode to validate if the request was successful.
ankitaj224 Oct 15, 2019
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
10 changes: 6 additions & 4 deletions firebase-installations/api.txt
Original file line number Diff line number Diff line change
Expand Up @@ -45,6 +45,7 @@ package com.google.firebase.installations.local {
method @Nullable public abstract String getAuthToken();
method public abstract long getExpiresInSecs();
method @Nullable public abstract String getFirebaseInstallationId();
method @Nullable public abstract String getFisError();
method @Nullable public abstract String getRefreshToken();
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallation.RegistrationStatus getRegistrationStatus();
method public abstract long getTokenCreationEpochInSecs();
Expand All @@ -61,6 +62,7 @@ package com.google.firebase.installations.local {
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder setAuthToken(@Nullable String);
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder setExpiresInSecs(long);
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder setFirebaseInstallationId(@NonNull String);
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder setFisError(@Nullable String);
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder setRefreshToken(@Nullable String);
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder setRegistrationStatus(@NonNull com.google.firebase.installations.local.PersistedInstallation.RegistrationStatus);
method @NonNull public abstract com.google.firebase.installations.local.PersistedInstallationEntry.Builder setTokenCreationEpochInSecs(long);
Expand All @@ -80,10 +82,10 @@ package com.google.firebase.installations.remote {
public abstract class InstallationResponse {
ctor public InstallationResponse();
method @NonNull public static com.google.firebase.installations.remote.InstallationResponse.Builder builder();
method @NonNull public abstract InstallationTokenResult getAuthToken();
method @NonNull public abstract String getFid();
method @NonNull public abstract String getRefreshToken();
method @NonNull public abstract String getUri();
method @Nullable public abstract InstallationTokenResult getAuthToken();
method @Nullable public abstract String getFid();
method @Nullable public abstract String getRefreshToken();
method @Nullable public abstract String getUri();
method @NonNull public abstract com.google.firebase.installations.remote.InstallationResponse.Builder toBuilder();
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@
import com.google.firebase.installations.local.PersistedInstallation.RegistrationStatus;
import com.google.firebase.installations.local.PersistedInstallationEntry;
import com.google.firebase.installations.remote.FirebaseInstallationServiceClient;
import com.google.firebase.installations.remote.InstallationResponse;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
Expand Down Expand Up @@ -291,6 +292,32 @@ public void testGetId_PersistedInstallationOk_BackendError() throws Exception {
assertThat(updatedInstallationEntry).hasRegistrationStatus(RegistrationStatus.REGISTER_ERROR);
}

@Test
public void testGetId_ServerError_UnregisteredFID() throws Exception {
// Mocking server error on FIS createFirebaseInstallation, returns empty InstallationResponse
when(backendClientReturnsOk.createFirebaseInstallation(
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

where does this test stub a 500 error?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sorry for the confusion. Added a comment & changed the test name. Does this makes sense?

anyString(), anyString(), anyString(), anyString()))
.thenReturn(InstallationResponse.builder().build());

FirebaseInstallations firebaseInstallations =
new FirebaseInstallations(
executor, firebaseApp, backendClientReturnsOk, persistedInstallation, mockUtils);

Tasks.await(firebaseInstallations.getId());

PersistedInstallationEntry entryValue =
persistedInstallation.readPersistedInstallationEntryValue();
assertThat(entryValue).hasFid(TEST_FID_1);

// Waiting for Task that registers FID on the FIS Servers
executor.awaitTermination(500, TimeUnit.MILLISECONDS);

PersistedInstallationEntry updatedInstallationEntry =
persistedInstallation.readPersistedInstallationEntryValue();
assertThat(updatedInstallationEntry).hasFid(TEST_FID_1);
assertThat(updatedInstallationEntry).hasRegistrationStatus(RegistrationStatus.UNREGISTERED);
}

@Test
public void testGetId_PersistedInstallationError_BackendOk() throws InterruptedException {
FirebaseInstallations firebaseInstallations =
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -218,12 +218,18 @@ private final void doRegistration() {
persistedInstallation.readPersistedInstallationEntryValue();

// New FID needs to be created
if (persistedInstallationEntry.isErrored() || persistedInstallationEntry.isNotGenerated()) {
if (persistedInstallationEntry.isNotGenerated()) {
String fid = utils.createRandomFid();
persistFid(fid);
persistedInstallationEntry = persistedInstallation.readPersistedInstallationEntryValue();
}

if (persistedInstallationEntry.isErrored()) {
throw new FirebaseInstallationsException(
persistedInstallationEntry.getFisError(),
FirebaseInstallationsException.Status.SDK_INTERNAL_ERROR);
}

triggerOnStateReached(persistedInstallationEntry);

// FID needs to be registered
Expand Down Expand Up @@ -262,6 +268,7 @@ private final void doRegistration() {
PersistedInstallationEntry errorInstallationEntry =
persistedInstallationEntry
.toBuilder()
.setFisError(e.getMessage())
.setRegistrationStatus(RegistrationStatus.REGISTER_ERROR)
.build();
persistedInstallation.insertOrUpdatePersistedInstallationEntry(errorInstallationEntry);
Expand Down Expand Up @@ -296,15 +303,17 @@ private Void registerAndSaveFid(PersistedInstallationEntry persistedInstallation
/*fid= */ persistedInstallationEntry.getFirebaseInstallationId(),
/*projectID= */ firebaseApp.getOptions().getProjectId(),
/*appId= */ getApplicationId());
persistedInstallation.insertOrUpdatePersistedInstallationEntry(
PersistedInstallationEntry.builder()
.setFirebaseInstallationId(installationResponse.getFid())
.setRegistrationStatus(RegistrationStatus.REGISTERED)
.setAuthToken(installationResponse.getAuthToken().getToken())
.setRefreshToken(installationResponse.getRefreshToken())
.setExpiresInSecs(installationResponse.getAuthToken().getTokenExpirationTimestamp())
.setTokenCreationEpochInSecs(creationTime)
.build());
if (installationResponse.getFid() != null) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

fyi:
If a #createInstallation request gets a successful response, the field "fid" should always be set.

What happens here if the field "fid" is not set? What state will the SDK end up in?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is to differentiate between 200, 5xx & 4xx errors.

200 - createInstallation successful response - fid is not empty
5xx - retry once - empty response & fid
4xx - throws exception

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If that is what you are doing, then I would recommend your code to express that.
In this case, I would recommend that you check for the 200 HTTP-status instead of the "fid" field.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

For that: I have to include Response Code as a field in InstallationResponse. Is that what you recommend?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

since y'all control both sides of this exchange we should be able to use and trust the status codes. imo having canonical status codes as a classification for installation responses is a good idea.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ack. PTAL.

persistedInstallation.insertOrUpdatePersistedInstallationEntry(
PersistedInstallationEntry.builder()
.setFirebaseInstallationId(installationResponse.getFid())
.setRegistrationStatus(RegistrationStatus.REGISTERED)
.setAuthToken(installationResponse.getAuthToken().getToken())
.setRefreshToken(installationResponse.getRefreshToken())
.setExpiresInSecs(installationResponse.getAuthToken().getTokenExpirationTimestamp())
.setTokenCreationEpochInSecs(creationTime)
.build());
}

} catch (FirebaseException exception) {
throw new FirebaseInstallationsException(
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -53,9 +53,10 @@ class Utils {
* #AUTH_TOKEN_EXPIRATION_BUFFER_IN_SECS}.
*/
public boolean isAuthTokenExpired(PersistedInstallationEntry persistedInstallationEntry) {
return persistedInstallationEntry.getTokenCreationEpochInSecs()
+ persistedInstallationEntry.getExpiresInSecs()
> currentTimeInSecs() + AUTH_TOKEN_EXPIRATION_BUFFER_IN_SECS;
return persistedInstallationEntry.isRegistered()
&& persistedInstallationEntry.getTokenCreationEpochInSecs()
+ persistedInstallationEntry.getExpiresInSecs()
> currentTimeInSecs() + AUTH_TOKEN_EXPIRATION_BUFFER_IN_SECS;
}

/** Returns current time in seconds. */
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,9 @@ public abstract class PersistedInstallationEntry {

public abstract long getTokenCreationEpochInSecs();

@Nullable
public abstract String getFisError();

public boolean isRegistered() {
return getRegistrationStatus() == PersistedInstallation.RegistrationStatus.REGISTERED;
}
Expand Down Expand Up @@ -90,6 +93,9 @@ public abstract Builder setRegistrationStatus(
@NonNull
public abstract Builder setTokenCreationEpochInSecs(long value);

@NonNull
public abstract Builder setFisError(@Nullable String value);

@NonNull
public abstract PersistedInstallationEntry build();
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -118,17 +118,21 @@ public InstallationResponse createFirebaseInstallation(
}

int httpResponseCode = httpsURLConnection.getResponseCode();
switch (httpResponseCode) {
case 200:
return readCreateResponse(httpsURLConnection);
case 500:
retryCount++;
break;
default:
throw new FirebaseException(readErrorResponse(httpsURLConnection));

if (httpResponseCode == 200) {
return readCreateResponse(httpsURLConnection);
}
// Usually the FIS server recovers from errors: retry one time before giving up.
if (httpResponseCode >= 500 && httpResponseCode < 600) {
retryCount++;
continue;
}

// Unrecoverable server response or unknown error
throw new FirebaseException(readErrorResponse(httpsURLConnection));
}
throw new FirebaseException(INTERNAL_SERVER_ERROR_MESSAGE);
// Return empty installation response after max retries
return InstallationResponse.builder().build();
} catch (IOException e) {
throw new FirebaseException(String.format(NETWORK_ERROR_MESSAGE, e.getMessage()));
}
Expand Down Expand Up @@ -219,15 +223,18 @@ public InstallationTokenResult generateAuthToken(
httpsURLConnection.addRequestProperty("Authorization", "FIS_v2 " + refreshToken);

int httpResponseCode = httpsURLConnection.getResponseCode();
switch (httpResponseCode) {
case 200:
return readGenerateAuthTokenResponse(httpsURLConnection);
case 500:
retryCount++;
break;
default:
throw new FirebaseException(readErrorResponse(httpsURLConnection));

if (httpResponseCode == 200) {
return readGenerateAuthTokenResponse(httpsURLConnection);
}
// Usually the FIS server recovers from errors: retry one time before giving up.
if (httpResponseCode >= 500 && httpResponseCode < 600) {
retryCount++;
continue;
}

// Unrecoverable server response or unknown error
throw new FirebaseException(readErrorResponse(httpsURLConnection));
}
throw new FirebaseException(INTERNAL_SERVER_ERROR_MESSAGE);
} catch (IOException e) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -15,22 +15,23 @@
package com.google.firebase.installations.remote;

import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import com.google.auto.value.AutoValue;
import com.google.firebase.installations.InstallationTokenResult;

@AutoValue
public abstract class InstallationResponse {

@NonNull
@Nullable
public abstract String getUri();

@NonNull
@Nullable
public abstract String getFid();

@NonNull
@Nullable
public abstract String getRefreshToken();

@NonNull
@Nullable
public abstract InstallationTokenResult getAuthToken();

@NonNull
Expand Down