Skip to content

Commit 54a0e3d

Browse files
authored
Merge f7d8315 into 27d7cf8
2 parents 27d7cf8 + f7d8315 commit 54a0e3d

File tree

4 files changed

+134
-146
lines changed

4 files changed

+134
-146
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,7 @@
3232

3333
### Fixes
3434

35+
- [ANR] Removed AndroidTransactionProfiler lock ([#4817](https://github.com/getsentry/sentry-java/pull/4817))
3536
- Avoid StrictMode warnings ([#4724](https://github.com/getsentry/sentry-java/pull/4724))
3637
- Use logger from options for JVM profiler ([#4771](https://github.com/getsentry/sentry-java/pull/4771))
3738
- Session Replay: Avoid deadlock when pausing replay if no connection ([#4788](https://github.com/getsentry/sentry-java/pull/4788))

sentry-android-core/src/main/java/io/sentry/android/core/AndroidProfiler.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -354,4 +354,8 @@ private void putPerformanceCollectionDataInMeasurements(
354354
}
355355
}
356356
}
357+
358+
boolean isRunning() {
359+
return isRunning;
360+
}
357361
}

sentry-android-core/src/main/java/io/sentry/android/core/AndroidTransactionProfiler.java

Lines changed: 109 additions & 126 deletions
Original file line numberDiff line numberDiff line change
@@ -5,8 +5,6 @@
55
import android.annotation.SuppressLint;
66
import android.content.Context;
77
import android.os.Build;
8-
import android.os.Process;
9-
import android.os.SystemClock;
108
import io.sentry.DateUtils;
119
import io.sentry.ILogger;
1210
import io.sentry.ISentryExecutorService;
@@ -26,9 +24,9 @@
2624
import java.util.ArrayList;
2725
import java.util.Date;
2826
import java.util.List;
27+
import java.util.concurrent.atomic.AtomicBoolean;
2928
import org.jetbrains.annotations.NotNull;
3029
import org.jetbrains.annotations.Nullable;
31-
import org.jetbrains.annotations.TestOnly;
3230

3331
final class AndroidTransactionProfiler implements ITransactionProfiler {
3432
private final @NotNull Context context;
@@ -39,10 +37,10 @@ final class AndroidTransactionProfiler implements ITransactionProfiler {
3937
private final @NotNull ISentryExecutorService executorService;
4038
private final @NotNull BuildInfoProvider buildInfoProvider;
4139
private boolean isInitialized = false;
42-
private int transactionsCounter = 0;
40+
private final @NotNull AtomicBoolean isRunning = new AtomicBoolean(false);
4341
private final @NotNull SentryFrameMetricsCollector frameMetricsCollector;
4442
private @Nullable ProfilingTransactionData currentProfilingTransactionData;
45-
private @Nullable AndroidProfiler profiler = null;
43+
private volatile @Nullable AndroidProfiler profiler = null;
4644
private long profileStartNanos;
4745
private long profileStartCpuMillis;
4846
private @NotNull Date profileStartTimestamp;
@@ -95,6 +93,7 @@ private void init() {
9593
return;
9694
}
9795
isInitialized = true;
96+
9897
if (!isProfilingEnabled) {
9998
logger.log(SentryLevel.INFO, "Profiling is disabled in options.");
10099
return;
@@ -124,22 +123,30 @@ private void init() {
124123

125124
@Override
126125
public void start() {
127-
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
128-
// Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler
129-
// causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392
130-
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return;
126+
// Debug.startMethodTracingSampling() is only available since Lollipop, but Android Profiler
127+
// causes crashes on api 21 -> https://github.com/getsentry/sentry-java/issues/3392
128+
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return;
131129

130+
// When the first transaction is starting, we can start profiling
131+
if (!isRunning.getAndSet(true)) {
132132
// Let's initialize trace folder and profiling interval
133133
init();
134134

135-
transactionsCounter++;
136-
// When the first transaction is starting, we can start profiling
137-
if (transactionsCounter == 1 && onFirstStart()) {
135+
if (onFirstStart()) {
138136
logger.log(SentryLevel.DEBUG, "Profiler started.");
139137
} else {
140-
transactionsCounter--;
141-
logger.log(
142-
SentryLevel.WARNING, "A profile is already running. This profile will be ignored.");
138+
// If profiler is not null and is running, it means that a profile is already running
139+
if (profiler != null && profiler.isRunning()) {
140+
logger.log(
141+
SentryLevel.WARNING, "A profile is already running. This profile will be ignored.");
142+
} else {
143+
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
144+
// Ensure we unbind any transaction data, just in case of concurrent starts
145+
currentProfilingTransactionData = null;
146+
}
147+
// Otherwise we update the flag, because it means the profiler is not running
148+
isRunning.set(false);
149+
}
143150
}
144151
}
145152
}
@@ -164,11 +171,14 @@ private boolean onFirstStart() {
164171

165172
@Override
166173
public void bindTransaction(final @NotNull ITransaction transaction) {
167-
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
168-
// If the profiler is running, but no profilingTransactionData is set, we bind it here
169-
if (transactionsCounter > 0 && currentProfilingTransactionData == null) {
170-
currentProfilingTransactionData =
171-
new ProfilingTransactionData(transaction, profileStartNanos, profileStartCpuMillis);
174+
// If the profiler is running, but no profilingTransactionData is set, we bind it here
175+
if (isRunning.get() && currentProfilingTransactionData == null) {
176+
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
177+
// If the profiler is running, but no profilingTransactionData is set, we bind it here
178+
if (isRunning.get() && currentProfilingTransactionData == null) {
179+
currentProfilingTransactionData =
180+
new ProfilingTransactionData(transaction, profileStartNanos, profileStartCpuMillis);
181+
}
172182
}
173183
}
174184
}
@@ -178,15 +188,13 @@ public void bindTransaction(final @NotNull ITransaction transaction) {
178188
final @NotNull ITransaction transaction,
179189
final @Nullable List<PerformanceCollectionData> performanceCollectionData,
180190
final @NotNull SentryOptions options) {
181-
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
182-
return onTransactionFinish(
183-
transaction.getName(),
184-
transaction.getEventId().toString(),
185-
transaction.getSpanContext().getTraceId().toString(),
186-
false,
187-
performanceCollectionData,
188-
options);
189-
}
191+
return onTransactionFinish(
192+
transaction.getName(),
193+
transaction.getEventId().toString(),
194+
transaction.getSpanContext().getTraceId().toString(),
195+
false,
196+
performanceCollectionData,
197+
options);
190198
}
191199

192200
@SuppressLint("NewApi")
@@ -197,20 +205,23 @@ public void bindTransaction(final @NotNull ITransaction transaction) {
197205
final boolean isTimeout,
198206
final @Nullable List<PerformanceCollectionData> performanceCollectionData,
199207
final @NotNull SentryOptions options) {
200-
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
201-
// check if profiler was created
202-
if (profiler == null) {
203-
return null;
204-
}
205208

206-
// onTransactionStart() is only available since Lollipop_MR1
207-
// and SystemClock.elapsedRealtimeNanos() since Jelly Bean
208-
// and SUPPORTED_ABIS since KITKAT
209-
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return null;
209+
// onTransactionStart() is only available since Lollipop_MR1
210+
// and SystemClock.elapsedRealtimeNanos() since Jelly Bean
211+
// and SUPPORTED_ABIS since KITKAT
212+
if (buildInfoProvider.getSdkInfoVersion() < Build.VERSION_CODES.LOLLIPOP_MR1) return null;
213+
214+
// check if profiler was created
215+
if (profiler == null) {
216+
return null;
217+
}
218+
219+
final ProfilingTransactionData txData;
220+
try (final @NotNull ISentryLifecycleToken ignored = lock.acquire()) {
221+
txData = currentProfilingTransactionData;
210222

211223
// Transaction finished, but it's not in the current profile
212-
if (currentProfilingTransactionData == null
213-
|| !currentProfilingTransactionData.getId().equals(transactionId)) {
224+
if (txData == null || !txData.getId().equals(transactionId)) {
214225
// A transaction is finishing, but it's not profiled. We can skip it
215226
logger.log(
216227
SentryLevel.INFO,
@@ -219,118 +230,90 @@ public void bindTransaction(final @NotNull ITransaction transaction) {
219230
traceId);
220231
return null;
221232
}
233+
currentProfilingTransactionData = null;
234+
}
222235

223-
if (transactionsCounter > 0) {
224-
transactionsCounter--;
225-
}
226-
227-
logger.log(SentryLevel.DEBUG, "Transaction %s (%s) finished.", transactionName, traceId);
236+
logger.log(SentryLevel.DEBUG, "Transaction %s (%s) finished.", transactionName, traceId);
228237

229-
if (transactionsCounter != 0) {
230-
// We notify the data referring to this transaction that it finished
231-
if (currentProfilingTransactionData != null) {
232-
currentProfilingTransactionData.notifyFinish(
233-
SystemClock.elapsedRealtimeNanos(),
234-
profileStartNanos,
235-
Process.getElapsedCpuTime(),
236-
profileStartCpuMillis);
237-
}
238-
return null;
239-
}
240-
241-
final AndroidProfiler.ProfileEndData endData =
242-
profiler.endAndCollect(false, performanceCollectionData);
243-
// check if profiler end successfully
244-
if (endData == null) {
245-
return null;
246-
}
238+
final AndroidProfiler.ProfileEndData endData =
239+
profiler.endAndCollect(false, performanceCollectionData);
247240

248-
long transactionDurationNanos = endData.endNanos - profileStartNanos;
241+
isRunning.set(false);
249242

250-
List<ProfilingTransactionData> transactionList = new ArrayList<>(1);
251-
final ProfilingTransactionData txData = currentProfilingTransactionData;
252-
if (txData != null) {
253-
transactionList.add(txData);
254-
}
255-
currentProfilingTransactionData = null;
256-
// We clear the counter in case of a timeout
257-
transactionsCounter = 0;
243+
// check if profiler end successfully
244+
if (endData == null) {
245+
return null;
246+
}
258247

259-
String totalMem = "0";
260-
final @Nullable Long memory =
261-
(options instanceof SentryAndroidOptions)
262-
? DeviceInfoUtil.getInstance(context, (SentryAndroidOptions) options).getTotalMemory()
263-
: null;
264-
if (memory != null) {
265-
totalMem = Long.toString(memory);
266-
}
267-
String[] abis = Build.SUPPORTED_ABIS;
248+
long transactionDurationNanos = endData.endNanos - profileStartNanos;
268249

269-
// We notify all transactions data that all transactions finished.
270-
// Some may not have been really finished, in case of a timeout
271-
for (ProfilingTransactionData t : transactionList) {
272-
t.notifyFinish(
273-
endData.endNanos, profileStartNanos, endData.endCpuMillis, profileStartCpuMillis);
274-
}
250+
final @NotNull List<ProfilingTransactionData> transactionList = new ArrayList<>(1);
251+
transactionList.add(txData);
252+
txData.notifyFinish(
253+
endData.endNanos, profileStartNanos, endData.endCpuMillis, profileStartCpuMillis);
275254

276-
// cpu max frequencies are read with a lambda because reading files is involved, so it will be
277-
// done in the background when the trace file is read
278-
return new ProfilingTraceData(
279-
endData.traceFile,
280-
profileStartTimestamp,
281-
transactionList,
282-
transactionName,
283-
transactionId,
284-
traceId,
285-
Long.toString(transactionDurationNanos),
286-
buildInfoProvider.getSdkInfoVersion(),
287-
abis != null && abis.length > 0 ? abis[0] : "",
288-
() -> CpuInfoUtils.getInstance().readMaxFrequencies(),
289-
buildInfoProvider.getManufacturer(),
290-
buildInfoProvider.getModel(),
291-
buildInfoProvider.getVersionRelease(),
292-
buildInfoProvider.isEmulator(),
293-
totalMem,
294-
options.getProguardUuid(),
295-
options.getRelease(),
296-
options.getEnvironment(),
297-
(endData.didTimeout || isTimeout)
298-
? ProfilingTraceData.TRUNCATION_REASON_TIMEOUT
299-
: ProfilingTraceData.TRUNCATION_REASON_NORMAL,
300-
endData.measurementsMap);
255+
String totalMem = "0";
256+
final @Nullable Long memory =
257+
(options instanceof SentryAndroidOptions)
258+
? DeviceInfoUtil.getInstance(context, (SentryAndroidOptions) options).getTotalMemory()
259+
: null;
260+
if (memory != null) {
261+
totalMem = Long.toString(memory);
301262
}
263+
final String[] abis = Build.SUPPORTED_ABIS;
264+
265+
// cpu max frequencies are read with a lambda because reading files is involved, so it will be
266+
// done in the background when the trace file is read
267+
return new ProfilingTraceData(
268+
endData.traceFile,
269+
profileStartTimestamp,
270+
transactionList,
271+
transactionName,
272+
transactionId,
273+
traceId,
274+
Long.toString(transactionDurationNanos),
275+
buildInfoProvider.getSdkInfoVersion(),
276+
abis != null && abis.length > 0 ? abis[0] : "",
277+
() -> CpuInfoUtils.getInstance().readMaxFrequencies(),
278+
buildInfoProvider.getManufacturer(),
279+
buildInfoProvider.getModel(),
280+
buildInfoProvider.getVersionRelease(),
281+
buildInfoProvider.isEmulator(),
282+
totalMem,
283+
options.getProguardUuid(),
284+
options.getRelease(),
285+
options.getEnvironment(),
286+
(endData.didTimeout || isTimeout)
287+
? ProfilingTraceData.TRUNCATION_REASON_TIMEOUT
288+
: ProfilingTraceData.TRUNCATION_REASON_NORMAL,
289+
endData.measurementsMap);
302290
}
303291

304292
@Override
305293
public boolean isRunning() {
306-
return transactionsCounter != 0;
294+
return isRunning.get();
307295
}
308296

309297
@Override
310298
public void close() {
299+
final @Nullable ProfilingTransactionData txData = currentProfilingTransactionData;
311300
// we stop profiling
312-
if (currentProfilingTransactionData != null) {
301+
if (txData != null) {
313302
onTransactionFinish(
314-
currentProfilingTransactionData.getName(),
315-
currentProfilingTransactionData.getId(),
316-
currentProfilingTransactionData.getTraceId(),
303+
txData.getName(),
304+
txData.getId(),
305+
txData.getTraceId(),
317306
true,
318307
null,
319308
ScopesAdapter.getInstance().getOptions());
320-
} else if (transactionsCounter != 0) {
321-
// in case the app start profiling is running, and it's not bound to a transaction, we still
322-
// stop profiling, but we also have to manually update the counter.
323-
transactionsCounter--;
324309
}
310+
// in case the app start profiling is running, and it's not bound to a transaction, we still
311+
// stop profiling, but we also have to manually update the flag.
312+
isRunning.set(false);
325313

326314
// we have to first stop profiling otherwise we would lost the last profile
327315
if (profiler != null) {
328316
profiler.close();
329317
}
330318
}
331-
332-
@TestOnly
333-
int getTransactionsCounter() {
334-
return transactionsCounter;
335-
}
336319
}

0 commit comments

Comments
 (0)