Skip to content

Commit 46e43bb

Browse files
authored
Fix performance collector setup called in main thread (#2499)
* ICollector.setup() calls have been moved to background thread
1 parent e199f18 commit 46e43bb

File tree

3 files changed

+48
-4
lines changed

3 files changed

+48
-4
lines changed

CHANGELOG.md

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

1616
### Fixes
1717

18+
- Fix performance collector setup called in main thread ([#2499](https://github.com/getsentry/sentry-java/pull/2499))
1819
- Expand guard against CVE-2018-9492 "Privilege Escalation via Content Provider" ([#2482](https://github.com/getsentry/sentry-java/pull/2482))
1920
- Prevent OOM by disabling TransactionPerformanceCollector for now ([#2498](https://github.com/getsentry/sentry-java/pull/2498))
2021

sentry/src/main/java/io/sentry/DefaultTransactionPerformanceCollector.java

Lines changed: 11 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -56,12 +56,20 @@ public void start(final @NotNull ITransaction transaction) {
5656
}
5757
if (!isStarted.getAndSet(true)) {
5858
synchronized (timerLock) {
59-
for (ICollector collector : collectors) {
60-
collector.setup();
61-
}
6259
if (timer == null) {
6360
timer = new Timer(true);
6461
}
62+
// We schedule the timer to call setup() on collectors immediately in the background.
63+
timer.schedule(
64+
new TimerTask() {
65+
@Override
66+
public void run() {
67+
for (ICollector collector : collectors) {
68+
collector.setup();
69+
}
70+
}
71+
},
72+
0L);
6573
// We schedule the timer to start after a delay, so we let some time pass between setup()
6674
// and collect() calls.
6775
// This way ICollectors that collect average stats based on time intervals, like

sentry/src/test/java/io/sentry/TransactionPerformanceCollectorTest.kt

Lines changed: 36 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,9 @@ package io.sentry
33
import io.sentry.test.getCtor
44
import io.sentry.test.getProperty
55
import io.sentry.test.injectForField
6+
import io.sentry.util.thread.MainThreadChecker
67
import org.mockito.kotlin.any
8+
import org.mockito.kotlin.atLeast
79
import org.mockito.kotlin.eq
810
import org.mockito.kotlin.mock
911
import org.mockito.kotlin.never
@@ -27,6 +29,7 @@ class TransactionPerformanceCollectorTest {
2729
private val className = "io.sentry.DefaultTransactionPerformanceCollector"
2830
private val ctorTypes: Array<Class<*>> = arrayOf(SentryOptions::class.java)
2931
private val fixture = Fixture()
32+
private val mainThreadChecker = MainThreadChecker.getInstance()
3033

3134
private class Fixture {
3235
lateinit var transaction1: ITransaction
@@ -71,7 +74,7 @@ class TransactionPerformanceCollectorTest {
7174
transaction1 = SentryTracer(TransactionContext("", ""), hub)
7275
transaction2 = SentryTracer(TransactionContext("", ""), hub)
7376
val collector = DefaultTransactionPerformanceCollector(options)
74-
val timer: Timer? = collector.getProperty("timer") ?: Timer(true)
77+
val timer: Timer = collector.getProperty("timer") ?: Timer(true)
7578
mockTimer = spy(timer)
7679
collector.injectForField("timer", mockTimer)
7780
return collector
@@ -101,6 +104,7 @@ class TransactionPerformanceCollectorTest {
101104
val cpuCollector = mock<ICollector>()
102105
val collector = fixture.getSut(memoryCollector, cpuCollector)
103106
collector.start(fixture.transaction1)
107+
Thread.sleep(300)
104108
verify(memoryCollector).setup()
105109
verify(cpuCollector).setup()
106110
}
@@ -197,4 +201,35 @@ class TransactionPerformanceCollectorTest {
197201
// We have the same number of memory and cpu data, even if we have 2 memory collectors and 1 cpu collector
198202
assertEquals(data1.memoryData.size, data1.cpuData.size)
199203
}
204+
205+
@Test
206+
fun `setup and collect happen on background thread`() {
207+
val threadCheckerCollector = spy(ThreadCheckerCollector())
208+
fixture.options.addCollector(threadCheckerCollector)
209+
val collector = fixture.getSut()
210+
// We have the ThreadCheckerCollector in the collectors
211+
assertTrue(fixture.options.collectors.any { it is ThreadCheckerCollector })
212+
213+
collector.start(fixture.transaction1)
214+
// Let's sleep to make the collector get values
215+
Thread.sleep(300)
216+
collector.stop(fixture.transaction1)
217+
218+
verify(threadCheckerCollector).setup()
219+
verify(threadCheckerCollector, atLeast(1)).collect(any())
220+
}
221+
222+
inner class ThreadCheckerCollector : ICollector {
223+
override fun setup() {
224+
if (mainThreadChecker.isMainThread) {
225+
throw AssertionError("setup() was called in the main thread")
226+
}
227+
}
228+
229+
override fun collect(performanceCollectionData: MutableIterable<PerformanceCollectionData>) {
230+
if (mainThreadChecker.isMainThread) {
231+
throw AssertionError("collect() was called in the main thread")
232+
}
233+
}
234+
}
200235
}

0 commit comments

Comments
 (0)