#1527 fixed concurrent region decoder pool access

This commit is contained in:
Thibault Deckers 2025-04-16 21:08:09 +02:00
parent af4ca96da8
commit 7f54befb72
3 changed files with 59 additions and 43 deletions

View file

@ -13,6 +13,10 @@ All notable changes to this project will be documented in this file.
- upgraded Flutter to stable v3.29.3 - upgraded Flutter to stable v3.29.3
### Fixed
- region decoding failing to access decoder pool
## <a id="v1.12.9"></a>[v1.12.9] - 2025-04-06 ## <a id="v1.12.9"></a>[v1.12.9] - 2025-04-06
### Added ### Added

View file

@ -20,6 +20,8 @@ import deckers.thibault.aves.utils.MemoryUtils
import deckers.thibault.aves.utils.MimeTypes import deckers.thibault.aves.utils.MimeTypes
import deckers.thibault.aves.utils.StorageUtils import deckers.thibault.aves.utils.StorageUtils
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import kotlin.math.max import kotlin.math.max
import kotlin.math.roundToInt import kotlin.math.roundToInt
@ -60,7 +62,7 @@ class RegionFetcher internal constructor(
} }
try { try {
val decoder = getOrCreateDecoder(uri, requestKey) val decoder = getOrCreateDecoder(context, uri, requestKey)
if (decoder == null) { if (decoder == null) {
result.error("fetch-read-null", "failed to open file for mimeType=$mimeType uri=$uri regionRect=$regionRect", null) result.error("fetch-read-null", "failed to open file for mimeType=$mimeType uri=$uri regionRect=$regionRect", null)
return return
@ -143,26 +145,6 @@ class RegionFetcher internal constructor(
} }
} }
private fun getOrCreateDecoder(uri: Uri, requestKey: Pair<Uri, Int?>): BitmapRegionDecoder? {
var decoderRef = decoderPool.firstOrNull { it.requestKey == requestKey }
if (decoderRef == null) {
val newDecoder = StorageUtils.openInputStream(context, uri)?.use { input ->
BitmapRegionDecoderCompat.newInstance(input)
}
if (newDecoder == null) {
return null
}
decoderRef = DecoderRef(requestKey, newDecoder)
} else {
decoderPool.remove(decoderRef)
}
decoderPool.add(0, decoderRef)
while (decoderPool.size > DECODER_POOL_SIZE) {
decoderPool.removeAt(decoderPool.size - 1)
}
return decoderRef.decoder
}
private fun createTemporaryJpegExport(uri: Uri, mimeType: String, pageId: Int?): Uri { private fun createTemporaryJpegExport(uri: Uri, mimeType: String, pageId: Int?): Uri {
Log.d(LOG_TAG, "create JPEG export for uri=$uri mimeType=$mimeType pageId=$pageId") Log.d(LOG_TAG, "create JPEG export for uri=$uri mimeType=$mimeType pageId=$pageId")
val target = Glide.with(context) val target = Glide.with(context)
@ -195,5 +177,29 @@ class RegionFetcher internal constructor(
private const val DECODER_POOL_SIZE = 3 private const val DECODER_POOL_SIZE = 3
private val decoderPool = ArrayList<DecoderRef>() private val decoderPool = ArrayList<DecoderRef>()
private val exportUris = HashMap<Pair<Uri, Int?>, Uri>() private val exportUris = HashMap<Pair<Uri, Int?>, Uri>()
private val poolLock = ReentrantLock()
private fun getOrCreateDecoder(context: Context, uri: Uri, requestKey: Pair<Uri, Int?>): BitmapRegionDecoder? {
poolLock.withLock {
var decoderRef = decoderPool.firstOrNull { it.requestKey == requestKey }
if (decoderRef == null) {
val newDecoder = StorageUtils.openInputStream(context, uri)?.use { input ->
BitmapRegionDecoderCompat.newInstance(input)
}
if (newDecoder == null) {
return null
}
decoderRef = DecoderRef(requestKey, newDecoder)
} else {
decoderPool.remove(decoderRef)
}
decoderPool.add(0, decoderRef)
while (decoderPool.size > DECODER_POOL_SIZE) {
decoderPool.removeAt(decoderPool.size - 1)
}
return decoderRef.decoder
}
}
} }
} }

View file

@ -17,6 +17,8 @@ import deckers.thibault.aves.utils.BitmapUtils
import deckers.thibault.aves.utils.MemoryUtils import deckers.thibault.aves.utils.MemoryUtils
import deckers.thibault.aves.utils.StorageUtils import deckers.thibault.aves.utils.StorageUtils
import io.flutter.plugin.common.MethodChannel import io.flutter.plugin.common.MethodChannel
import java.util.concurrent.locks.ReentrantLock
import kotlin.concurrent.withLock
import kotlin.math.ceil import kotlin.math.ceil
class SvgRegionFetcher internal constructor( class SvgRegionFetcher internal constructor(
@ -38,7 +40,7 @@ class SvgRegionFetcher internal constructor(
} }
try { try {
val svg = getOrCreateDecoder(uri) val svg = getOrCreateDecoder(context, uri)
if (svg == null) { if (svg == null) {
result.error("fetch-read-null", "failed to open file for uri=$uri regionRect=$regionRect", null) result.error("fetch-read-null", "failed to open file for uri=$uri regionRect=$regionRect", null)
return return
@ -95,7 +97,20 @@ class SvgRegionFetcher internal constructor(
} }
} }
private fun getOrCreateDecoder(uri: Uri): SVG? { private data class DecoderRef(
val uri: Uri,
val decoder: SVG,
)
companion object {
private val PREFERRED_CONFIG = Bitmap.Config.ARGB_8888
private const val DECODER_POOL_SIZE = 3
private val decoderPool = ArrayList<DecoderRef>()
private val poolLock = ReentrantLock()
private fun getOrCreateDecoder(context: Context, uri: Uri): SVG? {
poolLock.withLock {
var decoderRef = decoderPool.firstOrNull { it.uri == uri } var decoderRef = decoderPool.firstOrNull { it.uri == uri }
if (decoderRef == null) { if (decoderRef == null) {
val newDecoder = StorageUtils.openInputStream(context, uri)?.use { input -> val newDecoder = StorageUtils.openInputStream(context, uri)?.use { input ->
@ -115,15 +130,6 @@ class SvgRegionFetcher internal constructor(
} }
return decoderRef.decoder return decoderRef.decoder
} }
}
private data class DecoderRef(
val uri: Uri,
val decoder: SVG,
)
companion object {
private val PREFERRED_CONFIG = Bitmap.Config.ARGB_8888
private const val DECODER_POOL_SIZE = 3
private val decoderPool = ArrayList<DecoderRef>()
} }
} }