Skip to content

Commit e2a50d3

Browse files
committed
Fix ConcurrentModificationException in BitmapPool
1 parent 9ac1e2a commit e2a50d3

2 files changed

Lines changed: 52 additions & 14 deletions

File tree

  • features/dd-sdk-android-session-replay/src
    • main/kotlin/com/datadog/android/sessionreplay/internal/recorder/base64
    • test/kotlin/com/datadog/android/sessionreplay/internal/recorder/base64

features/dd-sdk-android-session-replay/src/main/kotlin/com/datadog/android/sessionreplay/internal/recorder/base64/BitmapPool.kt

Lines changed: 15 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -103,6 +103,21 @@ internal class BitmapPool(
103103
}?.apply { markBitmapAsUsed(this) }
104104
}
105105

106+
override fun onConfigurationChanged(newConfig: Configuration) {}
107+
108+
@Synchronized
109+
override fun onLowMemory() {
110+
bitmapPoolHelper.safeCall {
111+
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
112+
cache.evictAll()
113+
}
114+
}
115+
116+
@Synchronized
117+
override fun onTrimMemory(level: Int) {
118+
cacheUtils.handleTrimMemory(level, cache)
119+
}
120+
106121
internal fun getBitmapByProperties(width: Int, height: Int, config: Config): Bitmap? {
107122
val key = bitmapPoolHelper.generateKey(width, height, config)
108123
return get(key)
@@ -146,19 +161,6 @@ internal class BitmapPool(
146161
}
147162
}
148163

149-
override fun onConfigurationChanged(newConfig: Configuration) {}
150-
151-
override fun onLowMemory() {
152-
bitmapPoolHelper.safeCall {
153-
@Suppress("UnsafeThirdPartyFunctionCall") // Called within a try/catch block
154-
cache.evictAll()
155-
}
156-
}
157-
158-
override fun onTrimMemory(level: Int) {
159-
cacheUtils.handleTrimMemory(level, cache)
160-
}
161-
162164
internal companion object {
163165
@VisibleForTesting
164166
@Suppress("MagicNumber")

features/dd-sdk-android-session-replay/src/test/kotlin/com/datadog/android/sessionreplay/internal/recorder/base64/BitmapPoolTest.kt

Lines changed: 37 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,9 @@ import fr.xgouchet.elmyr.junit5.ForgeConfiguration
1717
import fr.xgouchet.elmyr.junit5.ForgeExtension
1818
import org.assertj.core.api.Assertions.assertThat
1919
import org.junit.jupiter.api.BeforeEach
20+
import org.junit.jupiter.api.RepeatedTest
2021
import org.junit.jupiter.api.Test
22+
import org.junit.jupiter.api.assertDoesNotThrow
2123
import org.junit.jupiter.api.extension.ExtendWith
2224
import org.junit.jupiter.api.extension.Extensions
2325
import org.mockito.Mock
@@ -32,6 +34,8 @@ import org.mockito.kotlin.verify
3234
import org.mockito.kotlin.whenever
3335
import org.mockito.quality.Strictness
3436
import java.util.concurrent.CountDownLatch
37+
import java.util.concurrent.Executors
38+
import java.util.concurrent.Future
3539
import java.util.concurrent.TimeUnit
3640

3741
@Extensions(
@@ -259,7 +263,7 @@ internal class BitmapPoolTest {
259263

260264
// Then
261265
countDownLatch.await(5, TimeUnit.SECONDS)
262-
assertThat(testedCache.bitmapsBySize.get(key)?.size).isEqualTo(3)
266+
assertThat(testedCache.bitmapsBySize[key]?.size).isEqualTo(3)
263267
}
264268

265269
@Test
@@ -358,6 +362,38 @@ internal class BitmapPoolTest {
358362
assertThat(captor.firstValue).isEqualTo(0)
359363
}
360364

365+
@RepeatedTest(30)
366+
fun `M not receive ConcurrentModificationException W get() { and onLowMemory }`(
367+
forge: Forge
368+
) {
369+
// when this issue occurs, the frequency is roughly once per 3000 runs,
370+
371+
// Given
372+
val numberOfThreads = 3
373+
val executor = Executors.newFixedThreadPool(numberOfThreads)
374+
val tasks = mutableListOf<Future<*>>()
375+
376+
val allThreadsStartedLatch = CountDownLatch(numberOfThreads)
377+
testedCache.put(mockBitmap)
378+
val bitmapKey = spyBitmapPoolHelper.generateKey(mockBitmap)
379+
380+
// When
381+
repeat(numberOfThreads) {
382+
tasks += executor.submit {
383+
allThreadsStartedLatch.countDown()
384+
allThreadsStartedLatch.await()
385+
if (forge.aBool()) {
386+
testedCache.get(bitmapKey)
387+
} else {
388+
testedCache.onLowMemory()
389+
}
390+
}
391+
}
392+
393+
// Then
394+
tasks.forEach { assertDoesNotThrow { it.get() } }
395+
}
396+
361397
// endregion
362398

363399
private fun createMockBitmap(forge: Forge): Bitmap {

0 commit comments

Comments
 (0)