Skip to content

Commit 3d502b8

Browse files
committed
Re-use thread
1 parent 2ff62db commit 3d502b8

2 files changed

Lines changed: 66 additions & 31 deletions

File tree

sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfilingIntegration.java

Lines changed: 54 additions & 25 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,8 @@ public class AnrProfilingIntegration
4848
private volatile @Nullable SentryAndroidOptions options;
4949
private volatile @Nullable Thread thread = null;
5050
private volatile boolean inForeground = false;
51+
private volatile @Nullable Handler mainHandler;
52+
private volatile @Nullable Thread mainThread;
5153

5254
@Override
5355
public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions options) {
@@ -63,17 +65,35 @@ public void register(final @NotNull IScopes scopes, final @NotNull SentryOptions
6365
return;
6466
}
6567

68+
final Looper mainLooper = Looper.getMainLooper();
69+
this.mainThread = mainLooper.getThread();
70+
this.mainHandler = new Handler(mainLooper);
71+
6672
addIntegrationToSdkVersion("AnrProfiling");
6773
AppState.getInstance().addAppStateListener(this);
6874
}
6975
}
7076

7177
@Override
7278
public void close() throws IOException {
73-
onBackground();
7479
enabled.set(false);
7580
AppState.getInstance().removeAppStateListener(this);
7681

82+
// Remove any pending updater callbacks from the main handler
83+
final @Nullable Handler handler = mainHandler;
84+
if (handler != null) {
85+
handler.removeCallbacks(updater);
86+
}
87+
88+
// Wake and interrupt the thread so it exits
89+
final @Nullable Thread t = thread;
90+
if (t != null) {
91+
synchronized (this) {
92+
notifyAll();
93+
}
94+
t.interrupt();
95+
}
96+
7797
final @Nullable SentryAndroidOptions opts = options;
7898
if (opts != null) {
7999
try {
@@ -95,7 +115,7 @@ public void run() {
95115
}
96116
}
97117
});
98-
} catch (Throwable t) {
118+
} catch (Throwable e) {
99119
logger.log(SentryLevel.WARNING, "Failed to submit AnrProfileManager close");
100120
}
101121
}
@@ -113,15 +133,19 @@ public void onForeground() {
113133
inForeground = true;
114134
updater.run();
115135

116-
final @Nullable Thread oldThread = thread;
117-
if (oldThread != null) {
118-
oldThread.interrupt();
136+
final @Nullable Thread existingThread = thread;
137+
if (existingThread != null && existingThread.isAlive()) {
138+
// Wake the existing thread
139+
synchronized (this) {
140+
notifyAll();
141+
}
142+
}
143+
if (existingThread == null || !existingThread.isAlive()) {
144+
final @NotNull Thread profilingThread = new Thread(this, "AnrProfilingIntegration");
145+
profilingThread.setDaemon(true);
146+
profilingThread.start();
147+
thread = profilingThread;
119148
}
120-
121-
final @NotNull Thread profilingThread = new Thread(this, "AnrProfilingIntegration");
122-
profilingThread.setDaemon(true);
123-
profilingThread.start();
124-
thread = profilingThread;
125149
}
126150
}
127151

@@ -131,32 +155,37 @@ public void onBackground() {
131155
return;
132156
}
133157
try (final @NotNull ISentryLifecycleToken ignored = lifecycleLock.acquire()) {
134-
if (!inForeground) {
135-
return;
136-
}
137-
138158
inForeground = false;
139-
final @Nullable Thread oldThread = thread;
140-
if (oldThread != null) {
141-
oldThread.interrupt();
142-
}
143159
}
144160
}
145161

146162
@Override
147163
public void run() {
148-
// get main thread Handler so we can post messages
149-
final Looper mainLooper = Looper.getMainLooper();
150-
final Thread mainThread = mainLooper.getThread();
151-
final Handler mainHandler = new Handler(mainLooper);
164+
final @Nullable Handler handler = mainHandler;
165+
final @Nullable Thread mt = mainThread;
166+
if (handler == null || mt == null) {
167+
return;
168+
}
152169

153170
try {
154171
while (enabled.get() && !Thread.currentThread().isInterrupted()) {
155172
try {
156-
checkMainThread(mainThread);
173+
if (!inForeground) {
174+
// Wait until we're back in the foreground or disabled
175+
synchronized (this) {
176+
while (!inForeground && enabled.get()) {
177+
wait();
178+
}
179+
}
180+
// Reset the updater timestamp after waking to avoid false suspicion
181+
updater.run();
182+
continue;
183+
}
184+
185+
checkMainThread(mt);
157186

158-
mainHandler.removeCallbacks(updater);
159-
mainHandler.post(updater);
187+
handler.removeCallbacks(updater);
188+
handler.post(updater);
160189

161190
// noinspection BusyWait
162191
Thread.sleep(POLLING_INTERVAL_MS);

sentry-android-core/src/test/java/io/sentry/android/core/anr/AnrProfilingIntegrationTest.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,7 @@ import kotlin.test.Test
1515
import kotlin.test.assertEquals
1616
import kotlin.test.assertFalse
1717
import kotlin.test.assertNotNull
18+
import kotlin.test.assertSame
1819
import kotlin.test.assertTrue
1920
import org.junit.Rule
2021
import org.junit.rules.TemporaryFolder
@@ -63,7 +64,7 @@ class AnrProfilingIntegrationTest {
6364
}
6465

6566
@Test
66-
fun `onBackground stops monitoring thread`() {
67+
fun `onBackground pauses monitoring thread`() {
6768
val integration = AnrProfilingIntegration()
6869
integration.register(mockScopes, options)
6970
integration.onForeground()
@@ -73,9 +74,14 @@ class AnrProfilingIntegrationTest {
7374
assertNotNull(thread)
7475

7576
integration.onBackground()
76-
thread.join(2000) // Wait for thread to stop
77+
Thread.sleep(200) // Allow thread to enter wait state
7778

78-
assertTrue(!thread.isAlive)
79+
// Thread should still be alive but waiting
80+
assertTrue(thread.isAlive)
81+
82+
integration.close()
83+
thread.join(2000)
84+
assertFalse(thread.isAlive)
7985
}
8086

8187
@Test
@@ -132,8 +138,7 @@ class AnrProfilingIntegrationTest {
132138
}
133139

134140
@Test
135-
fun `foreground after background restarts thread`() {
136-
// Arrange
141+
fun `foreground after background reuses thread`() {
137142
val integration = AnrProfilingIntegration()
138143
integration.register(mockScopes, options)
139144

@@ -149,7 +154,8 @@ class AnrProfilingIntegrationTest {
149154

150155
assertNotNull(thread1)
151156
assertNotNull(thread2)
152-
assertTrue(thread1 != thread2, "Should create a new thread after background")
157+
assertSame(thread1, thread2, "Should reuse the same thread after background")
158+
assertTrue(thread1.isAlive)
153159

154160
integration.close()
155161
}

0 commit comments

Comments
 (0)