From 3b9cfefd5f6925d8f97ea7b3cae0a9e619ae8602 Mon Sep 17 00:00:00 2001 From: "renovate[bot]" <99822064+revonateB0T@users.noreply.github.com> Date: Sun, 27 Oct 2024 20:01:53 +0800 Subject: [PATCH] Fix Small Preview CrossFade not work (#1816) * Fix Small Preview CrossFade not work * Abstract * Update MergeInterceptor.kt --------- Co-authored-by: Dude so hot --- .../coil/LimitConcurrencyNetworkClient.kt | 6 +-- .../hippo/ehviewer/coil/MergeInterceptor.kt | 10 +++- .../kotlin/moe/tarsin/coroutines/LockPool.kt | 47 +++++++++++++++++++ .../moe/tarsin/coroutines/NamedMutex.kt | 25 ++++------ .../moe/tarsin/coroutines/NamedSemaphore.kt | 25 ++++------ 5 files changed, 78 insertions(+), 35 deletions(-) create mode 100644 app/src/main/kotlin/moe/tarsin/coroutines/LockPool.kt diff --git a/app/src/main/kotlin/com/hippo/ehviewer/coil/LimitConcurrencyNetworkClient.kt b/app/src/main/kotlin/com/hippo/ehviewer/coil/LimitConcurrencyNetworkClient.kt index 575dc1097..57780b247 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/coil/LimitConcurrencyNetworkClient.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/coil/LimitConcurrencyNetworkClient.kt @@ -10,7 +10,7 @@ import io.ktor.client.HttpClient import kotlinx.coroutines.NonCancellable import kotlinx.coroutines.withContext import moe.tarsin.coroutines.NamedSemaphore -import moe.tarsin.coroutines.withPermit +import moe.tarsin.coroutines.withLock class LimitConcurrencyNetworkClient(val impl: NetworkClient) : NetworkClient { val semaphores = NamedSemaphore(permits = 8) @@ -18,9 +18,9 @@ class LimitConcurrencyNetworkClient(val impl: NetworkClient) : NetworkClient { val url = req.url return when { // Ex thumb server may not have h2 multiplexing support - URL_PREFIX_THUMB_EX in url -> semaphores.withPermit(URL_PREFIX_THUMB_EX) { withContext(NonCancellable) { impl.executeRequest(req, f) } } + URL_PREFIX_THUMB_EX in url -> semaphores.withLock(URL_PREFIX_THUMB_EX) { withContext(NonCancellable) { impl.executeRequest(req, f) } } // H@H server may not have h2 multiplexing support - URL_SIGNATURE_THUMB_NORMAL in url -> semaphores.withPermit(url.substringBefore(URL_SIGNATURE_THUMB_NORMAL)) { withContext(NonCancellable) { impl.executeRequest(req, f) } } + URL_SIGNATURE_THUMB_NORMAL in url -> semaphores.withLock(url.substringBefore(URL_SIGNATURE_THUMB_NORMAL)) { withContext(NonCancellable) { impl.executeRequest(req, f) } } // H2 multiplexing enabled else -> impl.executeRequest(req, f) } diff --git a/app/src/main/kotlin/com/hippo/ehviewer/coil/MergeInterceptor.kt b/app/src/main/kotlin/com/hippo/ehviewer/coil/MergeInterceptor.kt index b513f4e98..1ea318b64 100644 --- a/app/src/main/kotlin/com/hippo/ehviewer/coil/MergeInterceptor.kt +++ b/app/src/main/kotlin/com/hippo/ehviewer/coil/MergeInterceptor.kt @@ -1,11 +1,13 @@ package com.hippo.ehviewer.coil +import coil3.decode.DataSource import coil3.intercept.Interceptor import coil3.request.ImageResult +import coil3.request.SuccessResult import com.hippo.ehviewer.Settings import com.hippo.ehviewer.client.isNormalPreviewKey import moe.tarsin.coroutines.NamedMutex -import moe.tarsin.coroutines.withLock +import moe.tarsin.coroutines.withLockNeedSuspend object MergeInterceptor : Interceptor { private val mutex = NamedMutex() @@ -14,7 +16,11 @@ object MergeInterceptor : Interceptor { val req = chain.request val key = req.memoryCacheKey?.takeIf { it.isNormalPreviewKey || Settings.preloadThumbAggressively } return if (key != null) { - mutex.withLock(key) { chain.proceed() } + val (result, suspended) = mutex.withLockNeedSuspend(key) { chain.proceed() } + when (result) { + is SuccessResult if (suspended) -> result.copy(dataSource = DataSource.MEMORY) + else -> result + } } else { chain.proceed() } diff --git a/app/src/main/kotlin/moe/tarsin/coroutines/LockPool.kt b/app/src/main/kotlin/moe/tarsin/coroutines/LockPool.kt new file mode 100644 index 000000000..7e03105ad --- /dev/null +++ b/app/src/main/kotlin/moe/tarsin/coroutines/LockPool.kt @@ -0,0 +1,47 @@ +package moe.tarsin.coroutines + +import kotlin.contracts.InvocationKind +import kotlin.contracts.contract + +interface LockPool { + fun acquire(key: K): Lock + fun release(key: K, lock: Lock) + suspend fun Lock.lock() + fun Lock.tryLock(): Boolean + fun Lock.unlock() +} + +suspend inline fun LockPool.withLock(key: K, action: () -> R): R { + contract { + callsInPlace(action, InvocationKind.EXACTLY_ONCE) + } + val lock = acquire(key) + return try { + lock.lock() + return try { + action() + } finally { + lock.unlock() + } + } finally { + release(key, lock) + } +} + +suspend inline fun LockPool.withLockNeedSuspend(key: K, action: () -> R): Pair { + contract { + callsInPlace(action, InvocationKind.EXACTLY_ONCE) + } + val lock = acquire(key) + return try { + val mustSuspend = !lock.tryLock() + if (mustSuspend) lock.lock() + return try { + action() to mustSuspend + } finally { + lock.unlock() + } + } finally { + release(key, lock) + } +} diff --git a/app/src/main/kotlin/moe/tarsin/coroutines/NamedMutex.kt b/app/src/main/kotlin/moe/tarsin/coroutines/NamedMutex.kt index 7537e7025..16f2d893e 100644 --- a/app/src/main/kotlin/moe/tarsin/coroutines/NamedMutex.kt +++ b/app/src/main/kotlin/moe/tarsin/coroutines/NamedMutex.kt @@ -4,7 +4,6 @@ import androidx.collection.MutableScatterMap import androidx.collection.mutableScatterMapOf import io.ktor.utils.io.pool.DefaultPool import kotlinx.coroutines.sync.Mutex -import kotlinx.coroutines.sync.withLock class MutexTracker(mutex: Mutex = Mutex(), private var count: Int = 0) : Mutex by mutex { operator fun inc() = apply { count++ } @@ -21,20 +20,16 @@ object MutexPool : DefaultPool(capacity = 32) { } } -@JvmInline -value class NamedMutex(val active: MutableScatterMap = mutableScatterMapOf()) - -suspend inline fun NamedMutex.withLock(key: K, owner: Any? = null, action: () -> R): R { - val mutex = synchronized(active) { active.getOrPut(key) { MutexPool.borrow() }.inc() } - return try { - mutex.withLock(owner, action) - } finally { - synchronized(active) { - mutex.dec() - if (mutex.isFree) { - active.remove(key) - MutexPool.recycle(mutex) - } +class NamedMutex(val active: MutableScatterMap = mutableScatterMapOf()) : LockPool { + override fun acquire(key: K) = synchronized(active) { active.getOrPut(key) { MutexPool.borrow() }.inc() } + override fun release(key: K, lock: MutexTracker) = synchronized(active) { + lock.dec() + if (lock.isFree) { + active.remove(key) + MutexPool.recycle(lock) } } + override suspend fun MutexTracker.lock() = lock() + override fun MutexTracker.tryLock() = tryLock() + override fun MutexTracker.unlock() = unlock() } diff --git a/app/src/main/kotlin/moe/tarsin/coroutines/NamedSemaphore.kt b/app/src/main/kotlin/moe/tarsin/coroutines/NamedSemaphore.kt index 225c12fa0..9ce0c3060 100644 --- a/app/src/main/kotlin/moe/tarsin/coroutines/NamedSemaphore.kt +++ b/app/src/main/kotlin/moe/tarsin/coroutines/NamedSemaphore.kt @@ -3,7 +3,6 @@ package moe.tarsin.coroutines import androidx.collection.mutableScatterMapOf import io.ktor.utils.io.pool.DefaultPool import kotlinx.coroutines.sync.Semaphore -import kotlinx.coroutines.sync.withPermit class SemaphoreTracker(semaphore: Semaphore, private var count: Int = 0) : Semaphore by semaphore { operator fun inc() = apply { count++ } @@ -20,22 +19,18 @@ class SemaphorePool(val permits: Int) : DefaultPool(capacity = } } -class NamedSemaphore(val permits: Int) { +class NamedSemaphore(val permits: Int) : LockPool { val pool = SemaphorePool(permits = permits) val active = mutableScatterMapOf() -} - -suspend inline fun NamedSemaphore.withPermit(key: K, action: () -> R): R { - val semaphore = synchronized(active) { active.getOrPut(key) { pool.borrow() }.inc() } - return try { - semaphore.withPermit(action) - } finally { - synchronized(active) { - semaphore.dec() - if (semaphore.isFree) { - active.remove(key) - pool.recycle(semaphore) - } + override fun acquire(key: K) = synchronized(active) { active.getOrPut(key) { pool.borrow() }.inc() } + override fun release(key: K, lock: SemaphoreTracker) = synchronized(active) { + lock.dec() + if (lock.isFree) { + active.remove(key) + pool.recycle(lock) } } + override suspend fun SemaphoreTracker.lock() = acquire() + override fun SemaphoreTracker.tryLock() = tryAcquire() + override fun SemaphoreTracker.unlock() = release() }