Skip to content

Commit 9639e67

Browse files
authored
EmbraceAnrService cleanup (#2247)
* EmbraceAnrService cleanup * Add stacktraceSampler as a private dependency for EmbraceAnrService.kt * Stop exposing listeners in anrService, they were only used in tests * make processAnrTick private in EmbraceAnrService.kt, as it's only used in tests
1 parent 5381010 commit 9639e67

File tree

4 files changed

+79
-71
lines changed

4 files changed

+79
-71
lines changed

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/anr/EmbraceAnrService.kt

Lines changed: 25 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -19,47 +19,31 @@ import java.util.concurrent.TimeUnit
1919
* Checks whether the target thread is still responding by using the following strategy:
2020
*
2121
* 1. Creating a [android.os.Handler], on the target thread, and an executor on a monitor thread
22-
* 1. Using the 'monitoring' thread to message the target thread with a heartbeat
23-
* 1. Determining whether the target thread responds in time, and if not logging an ANR
22+
* 2. Using the 'monitoring' thread to message the target thread with a heartbeat
23+
* 3. Determining whether the target thread responds in time, and if not logging an ANR
2424
*/
2525
internal class EmbraceAnrService(
2626
private val configService: ConfigService,
27-
looper: Looper,
28-
logger: EmbLogger,
29-
livenessCheckScheduler: LivenessCheckScheduler,
27+
private val looper: Looper,
28+
private val logger: EmbLogger,
29+
private val livenessCheckScheduler: LivenessCheckScheduler,
3030
private val anrMonitorWorker: BackgroundWorker,
31-
state: ThreadMonitoringState,
32-
val clock: Clock,
31+
private val state: ThreadMonitoringState,
32+
private val clock: Clock,
33+
private val stacktraceSampler: AnrStacktraceSampler,
3334
) : AnrService, MemoryCleanerListener, ProcessStateListener, BlockedThreadListener {
3435

35-
private val state: ThreadMonitoringState
36-
private val targetThread: Thread
37-
val stacktraceSampler: AnrStacktraceSampler
38-
private val logger: EmbLogger
39-
private val targetThreadHeartbeatScheduler: LivenessCheckScheduler
40-
41-
val listeners: CopyOnWriteArrayList<BlockedThreadListener> = CopyOnWriteArrayList<BlockedThreadListener>()
36+
private val listeners: CopyOnWriteArrayList<BlockedThreadListener> = CopyOnWriteArrayList<BlockedThreadListener>()
4237

4338
init {
44-
targetThread = looper.thread
45-
this.logger = logger
46-
this.state = state
47-
targetThreadHeartbeatScheduler = livenessCheckScheduler
48-
stacktraceSampler = AnrStacktraceSampler(
49-
configService,
50-
clock,
51-
targetThread,
52-
anrMonitorWorker
53-
)
54-
5539
// add listeners
5640
listeners.add(stacktraceSampler)
5741
livenessCheckScheduler.listener = this
5842
}
5943

6044
override fun startAnrCapture() {
6145
this.anrMonitorWorker.submit {
62-
targetThreadHeartbeatScheduler.startMonitoringThread()
46+
livenessCheckScheduler.startMonitoringThread()
6347
}
6448
}
6549

@@ -89,7 +73,7 @@ internal class EmbraceAnrService(
8973

9074
override fun handleCrash(crashId: String) {
9175
this.anrMonitorWorker.submit {
92-
targetThreadHeartbeatScheduler.stopMonitoringThread()
76+
livenessCheckScheduler.stopMonitoringThread()
9377
}
9478
}
9579

@@ -116,26 +100,14 @@ internal class EmbraceAnrService(
116100
}
117101
}
118102

119-
fun processAnrTick(timestamp: Long) {
120-
// Check if ANR capture is enabled
121-
if (!configService.anrBehavior.isAnrCaptureEnabled()) {
122-
return
123-
}
124-
125-
// Invoke callbacks
126-
for (listener in listeners) {
127-
listener.onThreadBlockedInterval(targetThread, timestamp)
128-
}
129-
}
130-
131103
/**
132104
* When app goes to foreground, we need to monitor the target thread again to
133105
* capture ANRs.
134106
*/
135107
override fun onForeground(coldStart: Boolean, timestamp: Long) {
136108
this.anrMonitorWorker.submit {
137109
state.resetState()
138-
targetThreadHeartbeatScheduler.startMonitoringThread()
110+
livenessCheckScheduler.startMonitoringThread()
139111
}
140112
}
141113

@@ -146,7 +118,19 @@ internal class EmbraceAnrService(
146118
*/
147119
override fun onBackground(timestamp: Long) {
148120
this.anrMonitorWorker.submit {
149-
targetThreadHeartbeatScheduler.stopMonitoringThread()
121+
livenessCheckScheduler.stopMonitoringThread()
122+
}
123+
}
124+
125+
private fun processAnrTick(timestamp: Long) {
126+
// Check if ANR capture is enabled
127+
if (!configService.anrBehavior.isAnrCaptureEnabled()) {
128+
return
129+
}
130+
131+
// Invoke callbacks
132+
for (listener in listeners) {
133+
listener.onThreadBlockedInterval(looper.thread, timestamp)
150134
}
151135
}
152136

embrace-android-features/src/main/kotlin/io/embrace/android/embracesdk/internal/injection/AnrModuleImpl.kt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package io.embrace.android.embracesdk.internal.injection
33
import android.os.Looper
44
import io.embrace.android.embracesdk.internal.anr.AnrOtelMapper
55
import io.embrace.android.embracesdk.internal.anr.AnrService
6+
import io.embrace.android.embracesdk.internal.anr.AnrStacktraceSampler
67
import io.embrace.android.embracesdk.internal.anr.EmbraceAnrService
78
import io.embrace.android.embracesdk.internal.anr.detection.BlockedThreadDetector
89
import io.embrace.android.embracesdk.internal.anr.detection.LivenessCheckScheduler
@@ -31,7 +32,8 @@ internal class AnrModuleImpl(
3132
livenessCheckScheduler = livenessCheckScheduler,
3233
anrMonitorWorker = anrMonitorWorker,
3334
state = state,
34-
clock = initModule.clock
35+
clock = initModule.clock,
36+
stacktraceSampler = stacktraceSampler
3537
)
3638
} else {
3739
null
@@ -50,6 +52,15 @@ internal class AnrModuleImpl(
5052

5153
private val state by singleton { ThreadMonitoringState(initModule.clock) }
5254

55+
private val stacktraceSampler by singleton {
56+
AnrStacktraceSampler(
57+
configService = configService,
58+
clock = initModule.clock,
59+
targetThread = looper.thread,
60+
anrMonitorWorker = anrMonitorWorker
61+
)
62+
}
63+
5364
private val targetThreadHandler by singleton {
5465
TargetThreadHandler(
5566
looper = looper,

embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/anr/EmbraceAnrServiceRule.kt

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,6 +37,7 @@ internal class EmbraceAnrServiceRule<T : ScheduledExecutorService>(
3737
lateinit var anrExecutorService: T
3838
lateinit var targetThreadHandler: TargetThreadHandler
3939
lateinit var anrMonitorThread: AtomicReference<Thread>
40+
lateinit var stacktraceSampler: AnrStacktraceSampler
4041

4142
override fun before() {
4243
clock.setCurrentTime(0)
@@ -68,14 +69,21 @@ internal class EmbraceAnrServiceRule<T : ScheduledExecutorService>(
6869
blockedThreadDetector = blockedThreadDetector,
6970
logger = logger
7071
)
72+
stacktraceSampler = AnrStacktraceSampler(
73+
configService = fakeConfigService,
74+
clock = clock,
75+
targetThread = looper.thread,
76+
anrMonitorWorker = worker
77+
)
7178
anrService = EmbraceAnrService(
7279
configService = fakeConfigService,
7380
looper = looper,
7481
logger = logger,
7582
livenessCheckScheduler = livenessCheckScheduler,
7683
anrMonitorWorker = worker,
7784
state = state,
78-
clock = clock
85+
clock = clock,
86+
stacktraceSampler = stacktraceSampler
7987
)
8088
}
8189
}

embrace-android-features/src/test/java/io/embrace/android/embracesdk/internal/anr/EmbraceAnrServiceTest.kt

Lines changed: 33 additions & 28 deletions
Original file line numberDiff line numberDiff line change
@@ -62,8 +62,13 @@ internal class EmbraceAnrServiceTest {
6262
with(rule) {
6363
val listener = FakeBlockedThreadListener()
6464
anrService.addBlockedThreadListener(listener)
65-
assertEquals(anrService, blockedThreadDetector.listener)
66-
assertTrue(anrService.listeners.contains(listener))
65+
66+
// Test that the listener actually gets notified when thread blocking events occur
67+
anrService.onThreadBlocked(currentThread(), 1000L)
68+
assertEquals(1, listener.blockedCount)
69+
70+
anrService.onThreadUnblocked(currentThread(), 2000L)
71+
assertEquals(1, listener.unblockedCount)
6772
}
6873
}
6974

@@ -75,15 +80,15 @@ internal class EmbraceAnrServiceTest {
7580
anrService.onForeground(true, 0L)
7681

7782
// assert no ANR interval was added
78-
assertEquals(0, anrService.stacktraceSampler.anrIntervals.size)
83+
assertEquals(0, stacktraceSampler.anrIntervals.size)
7984
}
8085
}
8186

8287
@Test
8388
fun testCleanCollections() {
8489
with(rule) {
8590
// assert the ANR interval was added
86-
val anrIntervals = anrService.stacktraceSampler.anrIntervals
91+
val anrIntervals = stacktraceSampler.anrIntervals
8792
anrIntervals.add(AnrInterval(startTime = 15000000, endTime = 15000100))
8893
val inProgressInterval = AnrInterval(startTime = 15000000, lastKnownTime = 15000100)
8994
anrIntervals.add(inProgressInterval)
@@ -101,7 +106,7 @@ internal class EmbraceAnrServiceTest {
101106
@Test
102107
fun testGetIntervals() {
103108
with(rule) {
104-
populateAnrIntervals(anrService)
109+
populateAnrIntervals()
105110

106111
val anrIntervals = anrService.getCapturedData()
107112
assertEquals(5, anrIntervals.size)
@@ -153,8 +158,8 @@ internal class EmbraceAnrServiceTest {
153158
blockedThreadDetector.listener = anrService
154159
state.anrInProgress = true
155160
state.lastTargetThreadResponseMs = 15000000L
156-
anrService.processAnrTick(clock.now())
157-
assertEquals(1, anrService.stacktraceSampler.size())
161+
anrService.onThreadBlockedInterval(currentThread(), clock.now())
162+
assertEquals(1, stacktraceSampler.size())
158163

159164
// assert only one anr interval was added from the anrInProgress flag
160165
val anrIntervals = anrService.getCapturedData()
@@ -257,10 +262,9 @@ internal class EmbraceAnrServiceTest {
257262
}
258263
anrService.onThreadUnblocked(currentThread(), clock.now())
259264

260-
val sampler = anrService.stacktraceSampler
261-
assertEquals(1, sampler.anrIntervals.size)
265+
assertEquals(1, stacktraceSampler.anrIntervals.size)
262266

263-
val interval = checkNotNull(sampler.anrIntervals.first())
267+
val interval = checkNotNull(stacktraceSampler.anrIntervals.first())
264268
val samples = checkNotNull(interval.anrSampleList).samples
265269
assertEquals(count, samples.size)
266270

@@ -287,8 +291,8 @@ internal class EmbraceAnrServiceTest {
287291
// create an ANR service with config that disables ANR capture
288292
rule.anrBehavior.anrCaptureEnabled = false
289293
clock.setCurrentTime(15020000L)
290-
anrService.processAnrTick(clock.now())
291-
assertEquals(0, anrService.stacktraceSampler.size())
294+
anrService.onThreadBlockedInterval(currentThread(), clock.now())
295+
assertEquals(0, stacktraceSampler.size())
292296

293297
// assert no anr intervals were added
294298
val anrIntervals = anrService.getCapturedData()
@@ -300,16 +304,15 @@ internal class EmbraceAnrServiceTest {
300304
fun testReachedAnrCaptureLimit() {
301305
with(rule) {
302306
rule.anrBehavior.anrPerSessionImpl = 3
303-
val state = anrService.stacktraceSampler
304-
assertFalse(state.reachedAnrStacktraceCaptureLimit())
307+
assertFalse(stacktraceSampler.reachedAnrStacktraceCaptureLimit())
305308

306-
state.anrIntervals.add(AnrInterval(0, anrSampleList = AnrSampleList(listOf())))
307-
state.anrIntervals.add(AnrInterval(0, anrSampleList = AnrSampleList(listOf())))
308-
state.anrIntervals.add(AnrInterval(0, anrSampleList = AnrSampleList(listOf())))
309-
assertFalse(state.reachedAnrStacktraceCaptureLimit())
309+
stacktraceSampler.anrIntervals.add(AnrInterval(0, anrSampleList = AnrSampleList(listOf())))
310+
stacktraceSampler.anrIntervals.add(AnrInterval(0, anrSampleList = AnrSampleList(listOf())))
311+
stacktraceSampler.anrIntervals.add(AnrInterval(0, anrSampleList = AnrSampleList(listOf())))
312+
assertFalse(stacktraceSampler.reachedAnrStacktraceCaptureLimit())
310313

311-
state.anrIntervals.add(AnrInterval(0, anrSampleList = AnrSampleList(listOf())))
312-
assertTrue(state.reachedAnrStacktraceCaptureLimit())
314+
stacktraceSampler.anrIntervals.add(AnrInterval(0, anrSampleList = AnrSampleList(listOf())))
315+
assertTrue(stacktraceSampler.reachedAnrStacktraceCaptureLimit())
313316
}
314317
}
315318

@@ -401,21 +404,23 @@ internal class EmbraceAnrServiceTest {
401404
anrExecutorService.submit {
402405
assertTrue(state.started.get())
403406
}
404-
populateAnrIntervals(anrService)
407+
populateAnrIntervals()
405408
anrService.handleCrash("")
406409
val anrIntervals = anrService.getCapturedData()
407410
assertEquals(5, anrIntervals.size)
408411
assertFalse(state.started.get())
409412
}
410413
}
411414

412-
private fun populateAnrIntervals(anrService: EmbraceAnrService) {
413-
val state = anrService.stacktraceSampler
414-
state.anrIntervals.add(AnrInterval(startTime = 14000000L))
415-
state.anrIntervals.add(AnrInterval(startTime = 15000000L))
416-
state.anrIntervals.add(AnrInterval(startTime = 15000500L))
417-
state.anrIntervals.add(AnrInterval(startTime = 15001000L))
418-
state.anrIntervals.add(AnrInterval(startTime = 16000000L))
415+
private fun populateAnrIntervals() {
416+
with(rule) {
417+
val state = stacktraceSampler
418+
state.anrIntervals.add(AnrInterval(startTime = 14000000L))
419+
state.anrIntervals.add(AnrInterval(startTime = 15000000L))
420+
state.anrIntervals.add(AnrInterval(startTime = 15000500L))
421+
state.anrIntervals.add(AnrInterval(startTime = 15001000L))
422+
state.anrIntervals.add(AnrInterval(startTime = 16000000L))
423+
}
419424
}
420425

421426
/**

0 commit comments

Comments
 (0)