Ensure PageLoader not closed until decoder jobs completed (#1923)

* Optimize to use autoCloseScope

* Refine log

* Log

* Remove debug logging

* Refine

* Refine

---------

Co-authored-by: Dude so hot <djohn@fbi.gov>
This commit is contained in:
renovate[bot]andDude so hot authored and GitHub committed 2024-11-12 12:15:29 +08:00
1 parent c2c86ff58e
commit dd1bbf1226
4 files changed
+28 -33

No files matched your search

+1
View File
@@ -284,6 +284,7 @@ kotlin {
"-opt-in=androidx.compose.animation.ExperimentalAnimationApi",
"-opt-in=androidx.compose.animation.ExperimentalSharedTransitionApi",
"-opt-in=androidx.paging.ExperimentalPagingApi",
"-opt-in=kotlin.ExperimentalStdlibApi",
"-opt-in=kotlin.contracts.ExperimentalContracts",
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
"-opt-in=kotlinx.coroutines.FlowPreview",
@@ -1,5 +1,5 @@
/*
* Copyright 2023 Tarsin Norbin
* Copyright 2023-2024 Tarsin Norbin
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
@@ -15,9 +15,7 @@
*/
package com.hippo.ehviewer.gallery
import arrow.fx.coroutines.autoCloseable
import arrow.fx.coroutines.closeable
import arrow.fx.coroutines.resourceScope
import arrow.autoCloseScope
import com.hippo.ehviewer.Settings.archivePasswds
import com.hippo.ehviewer.image.ImageSource
import com.hippo.ehviewer.image.byteBufferSource
@@ -29,12 +27,10 @@ import com.hippo.ehviewer.jni.needPassword
import com.hippo.ehviewer.jni.openArchive
import com.hippo.ehviewer.jni.providePassword
import com.hippo.ehviewer.jni.releaseByteBuffer
import com.hippo.ehviewer.ui.screen.implicit
import com.hippo.ehviewer.util.FileUtils
import com.hippo.ehviewer.util.displayName
import com.hippo.files.openFileDescriptor
import eu.kanade.tachiyomi.util.system.logcat
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import okio.Path
@@ -49,19 +45,19 @@ suspend fun <T> useArchivePageLoader(
hasAds: Boolean = false,
passwdProvider: PasswdProvider = emptyPasswdProvider,
block: suspend (PageLoader) -> T,
) = coroutineScope {
resourceScope {
val pfd = closeable { file.openFileDescriptor("r") }
val size = install(
) = autoCloseScope {
coroutineScope {
val pfd = install(file.openFileDescriptor("r"))
val size = autoClose(
{ openArchive(pfd.fd, pfd.statSize, gid == 0L || file.name.endsWith(".zip")) },
{ _, _ -> closeArchive() },
)
check(size > 0) { "Archive have no content!" }
if (needPassword() && archivePasswds.filterNotNull().none { providePassword(it) }) {
archivePasswds += passwdProvider { providePassword(it) }
if (needPassword() && archivePasswds.filterNotNull().none(::providePassword)) {
archivePasswds += passwdProvider(::providePassword)
}
val loader = autoCloseable {
object : PageLoader(gid, startPage, size, hasAds, implicit<CoroutineScope>()) {
val loader = install(
object : PageLoader(gid, startPage, size, hasAds) {
override val title by lazy { FileUtils.getNameFromFilename(file.displayName)!! }
override fun getImageExtension(index: Int) = getExtension(index)
@@ -85,8 +81,8 @@ suspend fun <T> useArchivePageLoader(
override fun prefetchPages(pages: List<Int>, bounds: IntRange) = pages.forEach(::notifySourceReady)
override fun onRequest(index: Int, force: Boolean, orgImg: Boolean) = notifySourceReady(index)
}
}
},
)
loader.progressJob.join()
block(loader)
}
@@ -1,16 +1,13 @@
package com.hippo.ehviewer.gallery
import arrow.fx.coroutines.autoCloseable
import arrow.fx.coroutines.resourceScope
import arrow.autoCloseScope
import com.hippo.ehviewer.client.EhUtils
import com.hippo.ehviewer.client.data.GalleryInfo
import com.hippo.ehviewer.spider.SpiderQueen
import com.hippo.ehviewer.spider.SpiderQueen.Companion.obtainSpiderQueen
import com.hippo.ehviewer.spider.SpiderQueen.Companion.releaseSpiderQueen
import com.hippo.ehviewer.spider.SpiderQueen.OnSpiderListener
import com.hippo.ehviewer.ui.screen.implicit
import com.hippo.ehviewer.util.hasAds
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.coroutineScope
import okio.Path
@@ -18,15 +15,15 @@ suspend fun <T> useEhPageLoader(
info: GalleryInfo,
startPage: Int,
block: suspend (PageLoader) -> T,
) = coroutineScope {
resourceScope {
val queen = install(
) = autoCloseScope {
coroutineScope {
val queen = autoClose(
{ obtainSpiderQueen(info, SpiderQueen.MODE_READ) },
{ queen, _ -> releaseSpiderQueen(queen, SpiderQueen.MODE_READ) },
)
queen.awaitReady()
val loader = autoCloseable {
object : PageLoader(info.gid, startPage, queen.size, info.hasAds, implicit<CoroutineScope>()) {
val loader = install(
object : PageLoader(info.gid, startPage, queen.size, info.hasAds) {
override val title by lazy { EhUtils.getSuitableTitle(info) }
override fun getImageExtension(index: Int) = queen.getExtension(index)
@@ -38,8 +35,8 @@ suspend fun <T> useEhPageLoader(
override fun prefetchPages(pages: List<Int>, bounds: IntRange) = queen.preloadPages(pages, bounds)
override fun onRequest(index: Int, force: Boolean, orgImg: Boolean) = queen.request(index, force, orgImg)
}
}.apply {
},
).apply {
val listener = object : OnSpiderListener {
override fun onPageDownload(index: Int, contentLength: Long, receivedSize: Long, bytesRead: Int) {
if (contentLength > 0) {
@@ -51,12 +48,12 @@ suspend fun <T> useEhPageLoader(
override fun onPageFailure(index: Int, error: String?, finished: Int, downloaded: Int, total: Int) = notifyPageFailed(index, error)
}
install(
autoClose(
{ queen.addOnSpiderListener(listener) },
{ _, _ -> queen.removeOnSpiderListener(listener) },
)
progressJob.join()
}
loader.progressJob.join()
block(loader)
}
}
@@ -32,7 +32,8 @@ private val progressScope = CoroutineScope(Dispatchers.IO)
private const val MAX_CACHE_SIZE = 512 * 1024 * 1024
private const val MIN_CACHE_SIZE = 128 * 1024 * 1024
abstract class PageLoader(val gid: Long, var startPage: Int, val size: Int, val hasAds: Boolean = false, val scope: CoroutineScope) : AutoCloseable {
context(CoroutineScope)
abstract class PageLoader(val gid: Long, var startPage: Int, val size: Int, val hasAds: Boolean = false) : AutoCloseable {
private val mutex = NamedMutex<Int>()
private val semaphore = Semaphore(4)
@@ -61,6 +62,7 @@ abstract class PageLoader(val gid: Long, var startPage: Int, val size: Int, val
pages[index].status !is PageStatus.Blocked
suspend fun atomicallyDecodeAndUpdate(index: Int) {
if (!needDecode(index)) return
try {
bracketCase(
{ openSource(index) },
@@ -171,11 +173,10 @@ abstract class PageLoader(val gid: Long, var startPage: Int, val size: Int, val
fun notifySourceReady(index: Int) {
if (needDecode(index)) {
scope.launch {
launch {
mutex.withLock(index) {
semaphore.withPermit {
// Double check
if (needDecode(index)) atomicallyDecodeAndUpdate(index)
atomicallyDecodeAndUpdate(index)
}
}
}