Fix Small Preview CrossFade not work (#1816)

* Fix Small Preview CrossFade not work

* Abstract

* Update MergeInterceptor.kt

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
This commit is contained in:
renovate[bot]andDude so hot authored and GitHub committed 2024-10-27 12:01:53 +00:00
1 parent c1b701f1f9
commit 3b9cfefd5f
5 files changed
+78 -35

No files matched your search

@@ -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<String>(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)
}
@@ -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<String>()
@@ -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()
}
@@ -0,0 +1,47 @@
package moe.tarsin.coroutines
import kotlin.contracts.InvocationKind
import kotlin.contracts.contract
interface LockPool<Lock, K> {
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 <Lock, K, R> LockPool<Lock, K>.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 <Lock, K, R> LockPool<Lock, K>.withLockNeedSuspend(key: K, action: () -> R): Pair<R, Boolean> {
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)
}
}
@@ -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<MutexTracker>(capacity = 32) {
}
}
@JvmInline
value class NamedMutex<K>(val active: MutableScatterMap<K, MutexTracker> = mutableScatterMapOf())
suspend inline fun <K, R> NamedMutex<K>.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<K>(val active: MutableScatterMap<K, MutexTracker> = mutableScatterMapOf()) : LockPool<MutexTracker, K> {
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()
}
@@ -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<SemaphoreTracker>(capacity =
}
}
class NamedSemaphore<K>(val permits: Int) {
class NamedSemaphore<K>(val permits: Int) : LockPool<SemaphoreTracker, K> {
val pool = SemaphorePool(permits = permits)
val active = mutableScatterMapOf<K, SemaphoreTracker>()
}
suspend inline fun <K, R> NamedSemaphore<K>.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()
}