Skip to content

M144_1 Release #5763

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

Open
wants to merge 8 commits into
base: releases/m144
Choose a base branch
from
Open
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
7 changes: 7 additions & 0 deletions firebase-config/CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -1,4 +1,11 @@
# Unreleased
* [fixed] Fixed a bug that could cause a crash if the app was backgrounded
while it was listening for real-time Remote Config updates. For more information, see
<a href="https://github.com/firebase/firebase-android-sdk/issues/5751"
class="external">GitHub Issue #5751</a>.


# 21.6.2
* [fixed] Fixed an issue that could cause [remote_config] personalizations to be logged early in
specific cases.
* [fixed] Fixed an issue where the connection to the real-time Remote Config backend could remain
Expand Down
2 changes: 1 addition & 1 deletion firebase-config/firebase-config.gradle.kts
Original file line number Diff line number Diff line change
Expand Up @@ -53,7 +53,7 @@ android {

dependencies {
// Firebase
implementation(project(":firebase-config-interop"))
implementation("com.google.firebase:firebase-config-interop:16.0.1")
implementation("com.google.firebase:firebase-annotations:16.2.0")
implementation("com.google.firebase:firebase-installations-interop:17.1.0")
implementation("com.google.firebase:firebase-abt:21.1.1") {
Expand Down
4 changes: 2 additions & 2 deletions firebase-config/gradle.properties
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@
# limitations under the License.
#

version=21.6.2
latestReleasedVersion=21.6.1
version=21.6.3
latestReleasedVersion=21.6.2
android.enableUnitTestBinaryResources=true

Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,6 @@ public class ConfigAutoFetch {
private final ConfigUpdateListener retryCallback;
private final ScheduledExecutorService scheduledExecutorService;
private final Random random;
private Boolean isInBackground;

public ConfigAutoFetch(
HttpURLConnection httpURLConnection,
Expand Down Expand Up @@ -100,7 +99,6 @@ private String parseAndValidateConfigUpdateMessage(String message) {
}

// Check connection and establish InputStream
// TODO: Refactor connection management so it's handled by ConfigRealtimeHttpClient.
@VisibleForTesting
public void listenForNotifications() {
if (httpURLConnection == null) {
Expand All @@ -112,23 +110,13 @@ public void listenForNotifications() {
handleNotifications(inputStream);
inputStream.close();
} catch (IOException ex) {
// If the real-time connection is at an unexpected lifecycle state when the app is
// backgrounded, it's expected closing the InputStream will throw an exception.
// Moving network management to a single place should remove the need for this check.
// See above todo.
if (!isInBackground) {
// Otherwise, the real-time server connection was closed due to a transient issue.
Log.d(TAG, "Real-time connection was closed due to an exception.", ex);
}
// Stream was interrupted due to a transient issue and the system will retry the connection.
Log.d(TAG, "Stream was cancelled due to an exception. Retrying the connection...", ex);
} finally {
httpURLConnection.disconnect();
}
}

public void setInBackground(Boolean inBackground) {
isInBackground = inBackground;
}

// Auto-fetch new config and execute callbacks on each new message
private void handleNotifications(InputStream inputStream) throws IOException {
BufferedReader reader = new BufferedReader((new InputStreamReader(inputStream, "utf-8")));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -94,9 +94,6 @@ public synchronized void setBackgroundState(boolean isInBackground) {
configRealtimeHttpClient.setRealtimeBackgroundState(isInBackground);
if (!isInBackground) {
beginRealtime();
} else {
// If we're in the background, close the client's real-time server connection.
configRealtimeHttpClient.closeRealtimeHttpStream();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -97,10 +97,6 @@ public class ConfigRealtimeHttpClient {
@GuardedBy("this")
private boolean isRealtimeDisabled;

// The HttpUrlConnection and auto-fetcher for this client. Only one of each exist at a time.
private HttpURLConnection httpURLConnection;
private ConfigAutoFetch configAutoFetch;

/** Flag to indicate whether or not the app is in the background or not. */
private boolean isInBackground;

Expand Down Expand Up @@ -293,20 +289,6 @@ private synchronized boolean canMakeHttpStreamConnection() {
&& !isInBackground;
}

/**
* The check and set http connection method are combined so that when canMakeHttpStreamConnection
* returns true, the same thread can mark isHttpConnectionIsRunning as true to prevent a race
* condition with another thread.
*/
private synchronized boolean checkAndSetHttpConnectionFlagIfNotRunning() {
boolean canMakeConnection = canMakeHttpStreamConnection();
if (canMakeConnection) {
setIsHttpConnectionRunning(true);
}

return canMakeConnection;
}

private String getRealtimeURL(String namespace) {
return String.format(
REALTIME_REGEX_URL,
Expand Down Expand Up @@ -386,6 +368,10 @@ public synchronized void retryHttpConnectionWhenBackoffEnds() {
}

private synchronized void makeRealtimeHttpConnection(long retryMilliseconds) {
if (!canMakeHttpStreamConnection()) {
return;
}

if (httpRetriesRemaining > 0) {
httpRetriesRemaining--;
scheduledExecutorService.schedule(
Expand All @@ -407,11 +393,6 @@ public void run() {

void setRealtimeBackgroundState(boolean backgroundState) {
isInBackground = backgroundState;

// Propagate to ConfigAutoFetch, too.
if (configAutoFetch != null) {
this.configAutoFetch.setInBackground(backgroundState);
}
}

private synchronized void resetRetryCount() {
Expand Down Expand Up @@ -488,7 +469,7 @@ private String parseForbiddenErrorResponseMessage(InputStream inputStream) {
*/
@SuppressLint({"VisibleForTests", "DefaultLocale"})
public void beginRealtimeHttpStream() {
if (!checkAndSetHttpConnectionFlagIfNotRunning()) {
if (!canMakeHttpStreamConnection()) {
return;
}

Expand All @@ -508,7 +489,7 @@ public void beginRealtimeHttpStream() {
this.scheduledExecutorService,
(completedHttpUrlConnectionTask) -> {
Integer responseCode = null;
httpURLConnection = null;
HttpURLConnection httpURLConnection = null;

try {
// If HTTP connection task failed throw exception to move to the catch block.
Expand All @@ -528,44 +509,31 @@ public void beginRealtimeHttpStream() {
metadataClient.resetRealtimeBackoff();

// Start listening for realtime notifications.
configAutoFetch = startAutoFetch(httpURLConnection);
ConfigAutoFetch configAutoFetch = startAutoFetch(httpURLConnection);
configAutoFetch.listenForNotifications();
}
} catch (IOException e) {
if (isInBackground) {
// It's possible the app was backgrounded while the connection was open, which
// threw an exception trying to read the response. No real error here, so treat
// this as a success, even if we haven't read a 200 response code yet.
resetRetryCount();
} else {
// If it's not in the background, there might have been a transient error so the
// client will retry the connection.
Log.d(
TAG,
"Exception connecting to real-time RC backend. Retrying the connection...",
e);
}
// Stream could not be open due to a transient issue and the system will retry the
// connection
// without user intervention.
Log.d(
TAG,
"Exception connecting to real-time RC backend. Retrying the connection...",
e);
} finally {
// The connection is closed explicitly when the app is backgrounded. Many methods
// on HttpUrlConnection cannot be called twice.
if (!isInBackground) {
this.closeRealtimeHttpStream();
}

// Either way indicate the HTTP connection is closed.
closeRealtimeHttpStream(httpURLConnection);
setIsHttpConnectionRunning(false);

// Update backoff metadata if the connection failed in the foreground.
boolean connectionFailed =
!isInBackground
&& (responseCode == null || isStatusCodeRetryable(responseCode));
responseCode == null || isStatusCodeRetryable(responseCode);
if (connectionFailed) {
updateBackoffMetadataWithLastFailedStreamConnectionTime(
new Date(clock.currentTimeMillis()));
}

// If responseCode is null then no connection was made to server and the SDK should
// still retry.
// still
// retry.
if (connectionFailed || responseCode == HttpURLConnection.HTTP_OK) {
retryHttpConnectionWhenBackoffEnds();
} else {
Expand All @@ -574,7 +542,8 @@ public void beginRealtimeHttpStream() {
"Unable to connect to the server. Try again in a few minutes. HTTP status code: %d",
responseCode);
// Return server message for when the Firebase Remote Config Realtime API is
// disabled and the server returns a 403
// disabled and
// the server returns a 403
if (responseCode == 403) {
errorMessage =
parseForbiddenErrorResponseMessage(httpURLConnection.getErrorStream());
Expand All @@ -591,29 +560,20 @@ public void beginRealtimeHttpStream() {
});
}

/** Close the {@link HttpURLConnection} maintained by this client. */
public void closeRealtimeHttpStream() {
// Pauses Http stream listening
public void closeRealtimeHttpStream(HttpURLConnection httpURLConnection) {
if (httpURLConnection != null) {
// Network operations must be off the main thread.
this.scheduledExecutorService.execute(
() -> {
httpURLConnection.disconnect();

// Explicitly close the input stream due to a bug in the Android okhttp implementation.
// See github.com/firebase/firebase-android-sdk/pull/808.
try {
httpURLConnection.getInputStream().close();
if (httpURLConnection.getErrorStream() != null) {
httpURLConnection.getErrorStream().close();
}
} catch (IOException | IllegalStateException e) {
// HttpUrlConnection enforces a strict lifecycle. If the connection is closed before
// the response is read, it'll throw an IllegalStateException. See docs:
// https://android.googlesource.com/platform/external/okhttp/+/602d5e4/okhttp/src/main/java/com/squareup/okhttp/internal/http/HttpConnection.java#43
}
});

setIsHttpConnectionRunning(false);
httpURLConnection.disconnect();

// Explicitly close the input stream due to a bug in the Android okhttp implementation.
// See github.com/firebase/firebase-android-sdk/pull/808.
try {
httpURLConnection.getInputStream().close();
if (httpURLConnection.getErrorStream() != null) {
httpURLConnection.getErrorStream().close();
}
} catch (IOException e) {
}
}
}
}
Loading