diff --git a/CHANGELOG.md b/CHANGELOG.md index 86d67c97b8..f5716c14a3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,5 +1,11 @@ # Changelog +## Unreleased + +### Fixes + +- Create timer in `TransactionPerformanceCollector` lazily ([#2478](https://github.com/getsentry/sentry-java/pull/2478)) + ## 6.12.0 ### Features diff --git a/sentry/src/main/java/io/sentry/TransactionPerformanceCollector.java b/sentry/src/main/java/io/sentry/TransactionPerformanceCollector.java index c150ad0118..b7ebb73af1 100644 --- a/sentry/src/main/java/io/sentry/TransactionPerformanceCollector.java +++ b/sentry/src/main/java/io/sentry/TransactionPerformanceCollector.java @@ -18,7 +18,7 @@ public final class TransactionPerformanceCollector { private static final long TRANSACTION_COLLECTION_INTERVAL_MILLIS = 100; private static final long TRANSACTION_COLLECTION_TIMEOUT_MILLIS = 30000; private final @NotNull Object timerLock = new Object(); - private volatile @NotNull Timer timer = new Timer(); + private volatile @Nullable Timer timer = null; private final @NotNull Map> memoryMap = new ConcurrentHashMap<>(); private @Nullable IMemoryCollector memoryCollector = null; @@ -60,6 +60,9 @@ public void start(final @NotNull ITransaction transaction) { } if (!isStarted.getAndSet(true)) { synchronized (timerLock) { + if (timer == null) { + timer = new Timer(true); + } timer.scheduleAtFixedRate( new TimerTask() { @Override @@ -87,8 +90,10 @@ public void run() { synchronized (timerLock) { List memoryData = memoryMap.remove(transaction.getEventId().toString()); if (memoryMap.isEmpty() && isStarted.getAndSet(false)) { - timer.cancel(); - timer = new Timer(); + if (timer != null) { + timer.cancel(); + timer = null; + } } return memoryData; } diff --git a/sentry/src/test/java/io/sentry/TransactionPerformanceCollectorTest.kt b/sentry/src/test/java/io/sentry/TransactionPerformanceCollectorTest.kt index 50c537f538..6986aa1312 100644 --- a/sentry/src/test/java/io/sentry/TransactionPerformanceCollectorTest.kt +++ b/sentry/src/test/java/io/sentry/TransactionPerformanceCollectorTest.kt @@ -32,7 +32,7 @@ class TransactionPerformanceCollectorTest { lateinit var transaction2: ITransaction val hub: IHub = mock() val options = SentryOptions() - lateinit var mockTimer: Timer + var mockTimer: Timer? = null var lastScheduledRunnable: Runnable? = null val mockExecutorService = object : ISentryExecutorService { @@ -56,7 +56,7 @@ class TransactionPerformanceCollectorTest { transaction1 = SentryTracer(TransactionContext("", ""), hub) transaction2 = SentryTracer(TransactionContext("", ""), hub) val collector = TransactionPerformanceCollector(options) - val timer: Timer = collector.getProperty("timer") + val timer: Timer? = collector.getProperty("timer") ?: Timer(true) mockTimer = spy(timer) collector.injectForField("timer", mockTimer) return collector @@ -77,14 +77,14 @@ class TransactionPerformanceCollectorTest { val collector = fixture.getSut(NoOpMemoryCollector.getInstance()) assertIs(fixture.options.memoryCollector) collector.start(fixture.transaction1) - verify(fixture.mockTimer, never()).scheduleAtFixedRate(any(), any(), any()) + verify(fixture.mockTimer, never())!!.scheduleAtFixedRate(any(), any(), any()) } @Test fun `when start, timer is scheduled every 100 milliseconds`() { val collector = fixture.getSut() collector.start(fixture.transaction1) - verify(fixture.mockTimer).scheduleAtFixedRate(any(), any(), eq(100)) + verify(fixture.mockTimer)!!.scheduleAtFixedRate(any(), any(), eq(100)) } @Test @@ -92,16 +92,16 @@ class TransactionPerformanceCollectorTest { val collector = fixture.getSut() collector.start(fixture.transaction1) collector.stop(fixture.transaction1) - verify(fixture.mockTimer).scheduleAtFixedRate(any(), any(), eq(100)) - verify(fixture.mockTimer).cancel() + verify(fixture.mockTimer)!!.scheduleAtFixedRate(any(), any(), eq(100)) + verify(fixture.mockTimer)!!.cancel() } @Test fun `stopping a not collected transaction return null`() { val collector = fixture.getSut() val data = collector.stop(fixture.transaction1) - verify(fixture.mockTimer, never()).scheduleAtFixedRate(any(), any(), eq(100)) - verify(fixture.mockTimer, never()).cancel() + verify(fixture.mockTimer, never())!!.scheduleAtFixedRate(any(), any(), eq(100)) + verify(fixture.mockTimer, never())!!.cancel() assertNull(data) } @@ -115,11 +115,11 @@ class TransactionPerformanceCollectorTest { val data1 = collector.stop(fixture.transaction1) // There is still a transaction running: the timer shouldn't stop now - verify(fixture.mockTimer, never()).cancel() + verify(fixture.mockTimer, never())!!.cancel() val data2 = collector.stop(fixture.transaction2) // There are no more transactions running: the time should stop now - verify(fixture.mockTimer).cancel() + verify(fixture.mockTimer)!!.cancel() // The data returned by the collector is not empty assertFalse(data1!!.isEmpty()) @@ -132,11 +132,11 @@ class TransactionPerformanceCollectorTest { collector.start(fixture.transaction1) // Let's sleep to make the collector get values Thread.sleep(200) - verify(fixture.mockTimer, never()).cancel() + verify(fixture.mockTimer, never())!!.cancel() // Let the timeout job stop the collector fixture.lastScheduledRunnable?.run() - verify(fixture.mockTimer).cancel() + verify(fixture.mockTimer)!!.cancel() // Data is returned even after the collector times out val data1 = collector.stop(fixture.transaction1)