Skip to content

Commit 1d344e8

Browse files
authored
Make a best effort attempt to flush reports at crash time (#4112)
This should allow us to upload reports for start-up crashes.
1 parent 4131616 commit 1d344e8

File tree

6 files changed

+92
-3
lines changed

6 files changed

+92
-3
lines changed

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/common/Utils.java

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -134,6 +134,29 @@ public static <T> T awaitEvenIfOnMainThread(Task<T> task)
134134
}
135135
}
136136

137+
/** Invokes latch.await(timeout, unit) uninterruptibly. */
138+
public static boolean awaitUninterruptibly(CountDownLatch latch, long timeout, TimeUnit unit) {
139+
boolean interrupted = false;
140+
try {
141+
long remainingNanos = unit.toNanos(timeout);
142+
long end = System.nanoTime() + remainingNanos;
143+
144+
while (true) {
145+
try {
146+
// CountDownLatch treats negative timeouts just like zero.
147+
return latch.await(remainingNanos, TimeUnit.NANOSECONDS);
148+
} catch (InterruptedException e) {
149+
interrupted = true;
150+
remainingNanos = end - System.nanoTime();
151+
}
152+
}
153+
} finally {
154+
if (interrupted) {
155+
Thread.currentThread().interrupt();
156+
}
157+
}
158+
}
159+
137160
/**
138161
* ExecutorService that is used exclusively by the awaitEvenIfOnMainThread function. If the
139162
* Continuation which counts down the latch is called on the same thread which is waiting on the

firebase-crashlytics/src/main/java/com/google/firebase/crashlytics/internal/send/ReportQueue.java

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,17 +14,22 @@
1414

1515
package com.google.firebase.crashlytics.internal.send;
1616

17+
import android.annotation.SuppressLint;
1718
import com.google.android.datatransport.Event;
19+
import com.google.android.datatransport.Priority;
1820
import com.google.android.datatransport.Transport;
21+
import com.google.android.datatransport.runtime.ForcedSender;
1922
import com.google.android.gms.tasks.TaskCompletionSource;
2023
import com.google.firebase.crashlytics.internal.Logger;
2124
import com.google.firebase.crashlytics.internal.common.CrashlyticsReportWithSessionId;
2225
import com.google.firebase.crashlytics.internal.common.OnDemandCounter;
26+
import com.google.firebase.crashlytics.internal.common.Utils;
2327
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport;
2428
import com.google.firebase.crashlytics.internal.settings.Settings;
2529
import java.util.Locale;
2630
import java.util.concurrent.ArrayBlockingQueue;
2731
import java.util.concurrent.BlockingQueue;
32+
import java.util.concurrent.CountDownLatch;
2833
import java.util.concurrent.ThreadPoolExecutor;
2934
import java.util.concurrent.TimeUnit;
3035

@@ -115,6 +120,18 @@ TaskCompletionSource<CrashlyticsReportWithSessionId> enqueueReport(
115120
}
116121
}
117122

123+
@SuppressLint("DiscouragedApi") // best effort only
124+
public void flushScheduledReportsIfAble() {
125+
CountDownLatch latch = new CountDownLatch(1);
126+
new Thread(
127+
() -> {
128+
ForcedSender.sendBlocking(transport, Priority.HIGHEST);
129+
latch.countDown();
130+
})
131+
.start();
132+
Utils.awaitUninterruptibly(latch, 2, TimeUnit.SECONDS);
133+
}
134+
118135
/** Send the report to Crashlytics through Google DataTransport. */
119136
private void sendReport(
120137
CrashlyticsReportWithSessionId reportWithSessionId,
@@ -128,6 +145,7 @@ private void sendReport(
128145
tcs.trySetException(error);
129146
return;
130147
}
148+
flushScheduledReportsIfAble();
131149
tcs.trySetResult(reportWithSessionId);
132150
});
133151
}
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
// Copyright 2022 Google LLC
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package com.google.android.datatransport.runtime;
16+
17+
import android.annotation.SuppressLint;
18+
import androidx.annotation.Discouraged;
19+
import androidx.annotation.WorkerThread;
20+
import com.google.android.datatransport.Priority;
21+
import com.google.android.datatransport.Transport;
22+
23+
@Discouraged(
24+
message =
25+
"TransportRuntime is not a realtime delivery system, don't use unless you absolutely must.")
26+
public final class ForcedSender {
27+
@WorkerThread
28+
public static void sendBlocking(Transport<?> transport, Priority priority) {
29+
@SuppressLint("DiscouragedApi")
30+
TransportContext context = getTransportContextOrThrow(transport).withPriority(priority);
31+
TransportRuntime.getInstance().getUploader().logAndUpdateState(context, 1);
32+
}
33+
34+
private static TransportContext getTransportContextOrThrow(Transport<?> transport) {
35+
if (transport instanceof TransportImpl) {
36+
return ((TransportImpl<?>) transport).getTransportContext();
37+
}
38+
throw new IllegalArgumentException("Expected instance of TransportImpl.");
39+
}
40+
41+
private ForcedSender() {}
42+
}

transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/TransportImpl.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,4 +57,8 @@ public void schedule(Event<T> event, TransportScheduleCallback callback) {
5757
.build(),
5858
callback);
5959
}
60+
61+
TransportContext getTransportContext() {
62+
return transportContext;
63+
}
6064
}

transport/transport-runtime/src/main/java/com/google/android/datatransport/runtime/scheduling/jobscheduling/Uploader.java

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@
1717
import android.content.Context;
1818
import android.net.ConnectivityManager;
1919
import android.net.NetworkInfo;
20+
import androidx.annotation.RestrictTo;
2021
import androidx.annotation.VisibleForTesting;
2122
import com.google.android.datatransport.Encoding;
2223
import com.google.android.datatransport.runtime.EncodedPayload;
@@ -111,7 +112,8 @@ public void upload(TransportContext transportContext, int attemptNumber, Runnabl
111112
});
112113
}
113114

114-
BackendResponse logAndUpdateState(TransportContext transportContext, int attemptNumber) {
115+
@RestrictTo(RestrictTo.Scope.LIBRARY_GROUP)
116+
public BackendResponse logAndUpdateState(TransportContext transportContext, int attemptNumber) {
115117
TransportBackend backend = backendRegistry.get(transportContext.getBackendName());
116118
long maxNextRequestWaitMillis = 0;
117119

transport/transport-runtime/transport-runtime.gradle

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,7 @@ thirdPartyLicenses {
101101

102102
dependencies {
103103
implementation project(':transport:transport-api')
104-
implementation 'androidx.annotation:annotation:1.1.0'
104+
implementation 'androidx.annotation:annotation:1.3.0'
105105
implementation 'javax.inject:javax.inject:1'
106106
implementation project(":encoders:firebase-encoders")
107107
implementation project(":encoders:firebase-encoders-proto")
@@ -132,4 +132,4 @@ dependencies {
132132
androidTestImplementation 'org.mockito:mockito-android:2.25.0'
133133

134134
androidTestAnnotationProcessor 'com.google.dagger:dagger-compiler:2.27'
135-
}
135+
}

0 commit comments

Comments
 (0)