Skip to content

Commit 1ddd000

Browse files
markushiclaude
andauthored
fix(replay): Prevent ANR by caching connection status instead of using blocking calls (#4891)
* fix(replay): Prevent ANR by caching connection status instead of blocking calls Use a cached connection status value that is updated via the onConnectionStatusChanged() callback instead of making blocking getConnectionStatus() calls in time-sensitive code paths. This prevents ANR issues in AndroidContinuousProfiler and ReplayIntegration. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com> * docs(changelog): Add ANR fix entry for PR #4891 * Address PR feedback * Apply suggestion from @markushi --------- Co-authored-by: Claude <noreply@anthropic.com>
1 parent dcc6bbf commit 1ddd000

File tree

5 files changed

+10
-8
lines changed

5 files changed

+10
-8
lines changed

CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@
88
- Fix visual artifacts for the Canvas strategy on some devices ([#4861](https://github.com/getsentry/sentry-java/pull/4861))
99
- [Config] Trim whitespace on properties path ([#4880](https://github.com/getsentry/sentry-java/pull/4880))
1010
- Only set `DefaultReplayBreadcrumbConverter` if replay is available ([#4888](https://github.com/getsentry/sentry-java/pull/4888))
11+
- Session Replay: Cache connection status instead of using blocking calls ([#4891](https://github.com/getsentry/sentry-java/pull/4891))
1112

1213
### Improvements
1314

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -190,6 +190,7 @@ private void start() {
190190
}
191191

192192
// If device is offline, we don't start the profiler, to avoid flooding the cache
193+
// TODO .getConnectionStatus() may be blocking, investigate if this can be done async
193194
if (scopes.getOptions().getConnectionStatusProvider().getConnectionStatus() == DISCONNECTED) {
194195
logger.log(SentryLevel.WARNING, "Device is offline. Stopping profiler.");
195196
// Let's stop and reset profiler id, as the profile is now broken anyway

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

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -203,6 +203,7 @@ private void setDeviceIO(final @NotNull Device device, final boolean includeDyna
203203
device.setBatteryTemperature(getBatteryTemperature(batteryIntent));
204204
}
205205

206+
// TODO .getConnectionStatus() may be blocking, investigate if this can be done async
206207
Boolean connected;
207208
switch (options.getConnectionStatusProvider().getConnectionStatus()) {
208209
case DISCONNECTED:

sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ public class ReplayIntegration(
9595
this.gestureRecorderProvider = gestureRecorderProvider
9696
}
9797

98+
@Volatile private var lastKnownConnectionStatus: ConnectionStatus = ConnectionStatus.UNKNOWN
9899
private var debugMaskingEnabled: Boolean = false
99100
private lateinit var options: SentryOptions
100101
private var scopes: IScopes? = null
@@ -219,7 +220,7 @@ public class ReplayIntegration(
219220

220221
if (
221222
isManualPause.get() ||
222-
options.connectionStatusProvider.connectionStatus == DISCONNECTED ||
223+
lastKnownConnectionStatus == DISCONNECTED ||
223224
scopes?.rateLimiter?.isActiveForCategory(All) == true ||
224225
scopes?.rateLimiter?.isActiveForCategory(Replay) == true
225226
) {
@@ -335,6 +336,8 @@ public class ReplayIntegration(
335336
}
336337

337338
override fun onConnectionStatusChanged(status: ConnectionStatus) {
339+
lastKnownConnectionStatus = status
340+
338341
if (captureStrategy !is SessionCaptureStrategy) {
339342
// we only want to stop recording when offline for session mode
340343
return
@@ -375,7 +378,7 @@ public class ReplayIntegration(
375378
private fun checkCanRecord() {
376379
if (
377380
captureStrategy is SessionCaptureStrategy &&
378-
(options.connectionStatusProvider.connectionStatus == DISCONNECTED ||
381+
(lastKnownConnectionStatus == DISCONNECTED ||
379382
scopes?.rateLimiter?.isActiveForCategory(All) == true ||
380383
scopes?.rateLimiter?.isActiveForCategory(Replay) == true)
381384
) {

sentry-android-replay/src/test/java/io/sentry/android/replay/ReplayIntegrationTest.kt

Lines changed: 2 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -120,7 +120,6 @@ class ReplayIntegrationTest {
120120
context: Context,
121121
sessionSampleRate: Double = 1.0,
122122
onErrorSampleRate: Double = 1.0,
123-
isOffline: Boolean = false,
124123
isRateLimited: Boolean = false,
125124
recorderProvider: (() -> Recorder)? = null,
126125
replayCaptureStrategyProvider: ((isFullSession: Boolean) -> CaptureStrategy)? = null,
@@ -130,9 +129,6 @@ class ReplayIntegrationTest {
130129
options.run {
131130
sessionReplay.onErrorSampleRate = onErrorSampleRate
132131
sessionReplay.sessionSampleRate = sessionSampleRate
133-
connectionStatusProvider = mock {
134-
on { connectionStatus }.thenReturn(if (isOffline) DISCONNECTED else CONNECTED)
135-
}
136132
}
137133
if (isRateLimited) {
138134
whenever(rateLimiter.isActiveForCategory(any())).thenReturn(true)
@@ -623,13 +619,13 @@ class ReplayIntegrationTest {
623619
context,
624620
recorderProvider = { recorder },
625621
replayCaptureStrategyProvider = { captureStrategy },
626-
isOffline = true,
627622
)
628623

629624
replay.register(fixture.scopes, fixture.options)
630625
replay.start()
631626
replay.onScreenshotRecorded(mock<Bitmap>())
632627

628+
replay.onConnectionStatusChanged(DISCONNECTED)
633629
verify(recorder).pause()
634630
}
635631

@@ -903,10 +899,10 @@ class ReplayIntegrationTest {
903899
context,
904900
recorderProvider = { recorder },
905901
replayCaptureStrategyProvider = { captureStrategy },
906-
isOffline = true,
907902
)
908903

909904
replay.register(fixture.scopes, fixture.options)
905+
replay.onConnectionStatusChanged(DISCONNECTED)
910906
replay.start()
911907

912908
replay.pause()

0 commit comments

Comments
 (0)