Revert "Reapply "Fix thread starvation caused by not yielding or using an inappropriate thread pool (#2955)""

This reverts commit 9c01119d24.

# Conflicts:
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderActivity.kt
#	app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt
This commit is contained in:
Jobobby04
2026-04-08 15:02:37 -04:00
parent 170358f88e
commit 509ace1a38
16 changed files with 125 additions and 209 deletions
@@ -303,10 +303,7 @@ object SettingsDataScreen : SearchableSettings {
val chapterCache = remember { Injekt.get<ChapterCache>() }
var cacheReadableSizeSema by remember { mutableIntStateOf(0) }
var cacheReadableSize by remember { mutableStateOf(context.stringResource(MR.strings.calculating)) }
LaunchedEffect(cacheReadableSizeSema) {
cacheReadableSize = chapterCache.getReadableSize()
}
val cacheReadableSize = remember(cacheReadableSizeSema) { chapterCache.readableSize }
// SY -->
val pagePreviewCache = remember { Injekt.get<PagePreviewCache>() }
@@ -9,18 +9,12 @@ import androidx.compose.material3.LinearProgressIndicator
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.draw.clip
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import eu.kanade.tachiyomi.util.storage.DiskUtil
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import tachiyomi.i18n.MR
import tachiyomi.presentation.core.components.material.padding
import tachiyomi.presentation.core.i18n.stringResource
@@ -51,24 +45,10 @@ private fun StorageInfo(
) {
val context = LocalContext.current
var available by remember(file) { mutableStateOf(-1L) }
var total by remember(file) { mutableStateOf(-1L) }
LaunchedEffect(file) {
available = withContext(Dispatchers.IO) { DiskUtil.getAvailableStorageSpace(file) }
total = withContext(Dispatchers.IO) { DiskUtil.getTotalStorageSpace(file) }
}
val availableText = if (available == -1L) {
stringResource(MR.strings.calculating)
} else {
Formatter.formatFileSize(context, available)
}
val totalText = if (total == -1L) {
stringResource(MR.strings.calculating)
} else {
Formatter.formatFileSize(context, total)
}
val available = remember(file) { DiskUtil.getAvailableStorageSpace(file) }
val availableText = remember(available) { Formatter.formatFileSize(context, available) }
val total = remember(file) { DiskUtil.getTotalStorageSpace(file) }
val totalText = remember(total) { Formatter.formatFileSize(context, total) }
Column(
verticalArrangement = Arrangement.spacedBy(MaterialTheme.padding.extraSmall),
@@ -78,15 +58,13 @@ private fun StorageInfo(
style = MaterialTheme.typography.header,
)
if (total > 0) {
LinearProgressIndicator(
modifier = Modifier
.clip(MaterialTheme.shapes.small)
.fillMaxWidth()
.height(12.dp),
progress = { (1 - (available / total.toFloat())) },
)
}
LinearProgressIndicator(
modifier = Modifier
.clip(MaterialTheme.shapes.small)
.fillMaxWidth()
.height(12.dp),
progress = { (1 - (available / total.toFloat())) },
)
Text(
text = stringResource(MR.strings.available_disk_space_info, availableText, totalText),
@@ -13,7 +13,6 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.drop
import kotlinx.coroutines.flow.launchIn
import kotlinx.coroutines.flow.onEach
import kotlinx.coroutines.withContext
import kotlinx.serialization.json.Json
import logcat.LogPriority
import okhttp3.Response
@@ -64,13 +63,17 @@ class ChapterCache(
*/
private val cacheDir: File = diskCache.directory
/**
* Returns real size of directory.
*/
private val realSize: Long
get() = DiskUtil.getDirectorySize(cacheDir)
/**
* Returns real size of directory in human readable format.
*/
suspend fun getReadableSize(): String = withContext(Dispatchers.IO) {
val size = DiskUtil.getDirectorySize(cacheDir)
Formatter.formatFileSize(context, size)
}
val readableSize: String
get() = Formatter.formatFileSize(context, realSize)
// --> EH
// Cache size is in MB
@@ -109,10 +109,10 @@ class DownloadManager(
return queueState.value.find { it.chapter.id == chapterId }
}
suspend fun startDownloadNow(chapterId: Long) {
fun startDownloadNow(chapterId: Long) {
val existingDownload = getQueuedDownloadOrNull(chapterId)
// If not in queue try to start a new download
val toAdd = existingDownload ?: Download.fromChapterId(chapterId) ?: return
val toAdd = existingDownload ?: runBlocking { Download.fromChapterId(chapterId) } ?: return
queueState.value.toMutableList().apply {
existingDownload?.let { remove(it) }
add(0, toAdd)
@@ -89,7 +89,7 @@ class DownloadStore(
/**
* Returns the list of downloads to restore. It should be called in a background thread.
*/
suspend fun restore(): List<Download> {
fun restore(): List<Download> {
val objs = preferences.all
.mapNotNull { it.value as? String }
.mapNotNull { deserialize(it) }
@@ -100,10 +100,10 @@ class DownloadStore(
val cachedManga = mutableMapOf<Long, Manga?>()
for ((mangaId, chapterId) in objs) {
val manga = cachedManga.getOrPut(mangaId) {
getManga.await(mangaId)
runBlocking { getManga.await(mangaId) }
} ?: continue
val source = sourceManager.get(manga.source) as? HttpSource ?: continue
val chapter = getChapter.await(chapterId) ?: continue
val chapter = runBlocking { getChapter.await(chapterId) } ?: continue
downloads.add(Download(source, manga, chapter))
}
}
@@ -25,6 +25,7 @@ import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.Job
import kotlinx.coroutines.SupervisorJob
import kotlinx.coroutines.async
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asFlow
@@ -49,6 +50,7 @@ import okhttp3.Response
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.storage.extension
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.launchNow
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.system.ImageUtil
import tachiyomi.core.common.util.system.logcat
@@ -119,9 +121,9 @@ class Downloader(
var isPaused: Boolean = false
init {
scope.launch {
val chapters = store.restore()
addAllToQueue(chapters)
launchNow {
val chapters = async { store.restore() }
addAllToQueue(chapters.await())
}
}
@@ -23,7 +23,6 @@ import kotlinx.coroutines.DelicateCoroutinesApi
import kotlinx.coroutines.runBlocking
import tachiyomi.core.common.Constants
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.domain.chapter.interactor.GetChapter
import tachiyomi.domain.chapter.interactor.UpdateChapter
import tachiyomi.domain.chapter.model.Chapter
@@ -85,18 +84,11 @@ class NotificationReceiver : BroadcastReceiver() {
ACTION_CANCEL_APP_UPDATE_DOWNLOAD -> cancelDownloadAppUpdate(context)
// Open reader activity
ACTION_OPEN_CHAPTER -> {
val pendingResult = goAsync()
launchIO {
try {
openChapter(
context,
intent.getLongExtra(EXTRA_MANGA_ID, -1),
intent.getLongExtra(EXTRA_CHAPTER_ID, -1),
)
} finally {
pendingResult.finish()
}
}
openChapter(
context,
intent.getLongExtra(EXTRA_MANGA_ID, -1),
intent.getLongExtra(EXTRA_CHAPTER_ID, -1),
)
}
// Mark updated manga chapters as read
ACTION_MARK_AS_READ -> {
@@ -161,18 +153,16 @@ class NotificationReceiver : BroadcastReceiver() {
* @param mangaId id of manga
* @param chapterId id of chapter
*/
private suspend fun openChapter(context: Context, mangaId: Long, chapterId: Long) {
val manga = getManga.await(mangaId)
val chapter = getChapter.await(chapterId)
withUIContext {
if (manga != null && chapter != null) {
val intent = ReaderActivity.newIntent(context, manga.id, chapter.id).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
context.startActivity(intent)
} else {
context.toast(MR.strings.chapter_error)
private fun openChapter(context: Context, mangaId: Long, chapterId: Long) {
val manga = runBlocking { getManga.await(mangaId) }
val chapter = runBlocking { getChapter.await(chapterId) }
if (manga != null && chapter != null) {
val intent = ReaderActivity.newIntent(context, manga.id, chapter.id).apply {
flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TOP
}
context.startActivity(intent)
} else {
context.toast(MR.strings.chapter_error)
}
}
@@ -30,7 +30,6 @@ import kotlinx.coroutines.flow.asStateFlow
import kotlinx.coroutines.flow.emptyFlow
import kotlinx.coroutines.flow.map
import kotlinx.coroutines.flow.stateIn
import kotlinx.coroutines.launch
import logcat.LogPriority
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat
@@ -141,22 +140,20 @@ class ExtensionManager(
* Loads and registers the installed extensions.
*/
private fun initExtensions() {
scope.launch {
val extensions = ExtensionLoader.loadExtensions(context)
val extensions = ExtensionLoader.loadExtensions(context)
installedExtensionMapFlow.value = extensions
.filterIsInstance<LoadResult.Success>()
.associate { it.extension.pkgName to it.extension }
installedExtensionMapFlow.value = extensions
.filterIsInstance<LoadResult.Success>()
.associate { it.extension.pkgName to it.extension }
untrustedExtensionMapFlow.value = extensions
.filterIsInstance<LoadResult.Untrusted>()
.associate { it.extension.pkgName to it.extension }
// SY -->
.filterNotBlacklisted()
// SY <--
untrustedExtensionMapFlow.value = extensions
.filterIsInstance<LoadResult.Untrusted>()
.associate { it.extension.pkgName to it.extension }
// SY -->
.filterNotBlacklisted()
// SY <--
_isInitialized.value = true
}
_isInitialized.value = true
}
// EXH -->
@@ -18,7 +18,6 @@ import eu.kanade.tachiyomi.util.storage.copyAndSetReadOnlyTo
import eu.kanade.tachiyomi.util.system.ChildFirstPathClassLoader
import kotlinx.coroutines.async
import kotlinx.coroutines.awaitAll
import kotlinx.coroutines.coroutineScope
import kotlinx.coroutines.runBlocking
import logcat.LogPriority
import tachiyomi.core.common.util.system.logcat
@@ -115,7 +114,7 @@ internal object ExtensionLoader {
*
* @param context The application context.
*/
suspend fun loadExtensions(context: Context): List<LoadResult> {
fun loadExtensions(context: Context): List<LoadResult> {
val pkgManager = context.packageManager
val installedPkgs = if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
@@ -161,10 +160,11 @@ internal object ExtensionLoader {
if (extPkgs.isEmpty()) return emptyList()
// Load each extension concurrently and wait for completion
return coroutineScope {
extPkgs.map {
return runBlocking {
val deferred = extPkgs.map {
async { loadExtension(context, it) }
}.awaitAll()
}
deferred.awaitAll()
}
}
@@ -163,6 +163,13 @@ class MainActivity : BaseActivity() {
super.onCreate(savedInstanceState)
val didMigration = if (isLaunch) {
addAnalytics()
Migrator.awaitAndRelease()
} else {
false
}
// Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079
if (!isTaskRoot) {
finish()
@@ -175,12 +182,6 @@ class MainActivity : BaseActivity() {
// SY <--
setComposeContent {
var didMigration by remember { mutableStateOf<Boolean?>(null) }
LaunchedEffect(Unit) {
addAnalytics()
didMigration = Migrator.awaitAndRelease()
}
val context = LocalContext.current
var incognito by remember { mutableStateOf(getIncognitoState.await(null)) }
@@ -308,7 +309,7 @@ class MainActivity : BaseActivity() {
}
// SY <--
var showChangelog by remember { mutableStateOf(didMigration == true && !BuildConfig.DEBUG) }
var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) }
if (showChangelog) {
// SY -->
WhatsNewDialog(onDismissRequest = { showChangelog = false })
@@ -32,7 +32,6 @@ import androidx.compose.material3.CircularProgressIndicator
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.collectAsState
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
@@ -79,7 +78,6 @@ import eu.kanade.tachiyomi.ui.main.MainActivity
import eu.kanade.tachiyomi.ui.reader.ReaderViewModel.SetAsCoverResult.AddToLibraryFirst
import eu.kanade.tachiyomi.ui.reader.ReaderViewModel.SetAsCoverResult.Error
import eu.kanade.tachiyomi.ui.reader.ReaderViewModel.SetAsCoverResult.Success
import eu.kanade.tachiyomi.ui.reader.chapter.ReaderChapterItem
import eu.kanade.tachiyomi.ui.reader.loader.HttpPageLoader
import eu.kanade.tachiyomi.ui.reader.model.ReaderChapter
import eu.kanade.tachiyomi.ui.reader.model.ReaderPage
@@ -103,8 +101,6 @@ import exh.source.isEhBasedSource
import exh.ui.ifSourcesLoaded
import exh.util.defaultReaderType
import exh.util.mangaType
import kotlinx.collections.immutable.ImmutableList
import kotlinx.collections.immutable.persistentListOf
import kotlinx.collections.immutable.persistentSetOf
import kotlinx.collections.immutable.toImmutableList
import kotlinx.collections.immutable.toImmutableSet
@@ -125,7 +121,6 @@ import tachiyomi.core.common.i18n.pluralStringResource
import tachiyomi.core.common.i18n.stringResource
import tachiyomi.core.common.util.lang.launchIO
import tachiyomi.core.common.util.lang.launchNonCancellable
import tachiyomi.core.common.util.lang.withIOContext
import tachiyomi.core.common.util.lang.withUIContext
import tachiyomi.core.common.util.system.logcat
import tachiyomi.domain.source.service.SourceManager
@@ -399,36 +394,28 @@ class ReaderActivity : BaseActivity() {
is ReaderViewModel.Dialog.ChapterList -> {
var chapters by remember {
mutableStateOf<ImmutableList<ReaderChapterItem>?>(null)
}
LaunchedEffect(state.dialog) {
withIOContext {
chapters = viewModel.getChapters().toImmutableList()
}
}
if (chapters != null) {
ChapterListDialog(
onDismissRequest = onDismissRequest,
screenModel = settingsScreenModel,
chapters = chapters ?: persistentListOf(),
onClickChapter = {
viewModel.loadNewChapterFromDialog(it)
onDismissRequest()
},
onBookmark = { chapter ->
viewModel.toggleBookmark(chapter.id, !chapter.bookmark)
chapters = chapters?.map {
if (it.chapter.id == chapter.id) {
it.copy(chapter = chapter.copy(bookmark = !chapter.bookmark))
} else {
it
}
}?.toImmutableList()
},
state.dateRelativeTime,
)
mutableStateOf(viewModel.getChapters().toImmutableList())
}
ChapterListDialog(
onDismissRequest = onDismissRequest,
screenModel = settingsScreenModel,
chapters = chapters,
onClickChapter = {
viewModel.loadNewChapterFromDialog(it)
onDismissRequest()
},
onBookmark = { chapter ->
viewModel.toggleBookmark(chapter.id, !chapter.bookmark)
chapters = chapters.map {
if (it.chapter.id == chapter.id) {
it.copy(chapter = chapter.copy(bookmark = !chapter.bookmark))
} else {
it
}
}.toImmutableList()
},
state.dateRelativeTime,
)
}
// SY -->
ReaderViewModel.Dialog.AutoScrollHelp -> AlertDialog(
@@ -604,9 +591,8 @@ class ReaderActivity : BaseActivity() {
} else {
cropBorderContinuousVertical
}
val readerBottomButtons by remember {
readerPreferences.readerBottomButtons.changes().map { it.toImmutableSet() }
}.collectAsState(persistentSetOf())
val readerBottomButtons by readerPreferences.readerBottomButtons.changes().map { it.toImmutableSet() }
.collectAsState(persistentSetOf())
val dualPageSplitPaged by readerPreferences.dualPageSplitPaged.collectAsState()
val forceHorizontalSeekbar by readerPreferences.forceHorizontalSeekbar.collectAsState()
@@ -59,6 +59,7 @@ import exh.source.isEhBasedManga
import exh.util.defaultReaderType
import exh.util.mangaType
import kotlinx.coroutines.CancellationException
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.asStateFlow
@@ -182,26 +183,19 @@ class ReaderViewModel @JvmOverloads constructor(
private var chapterToDownload: Download? = null
private var unfilteredChapterListCache: List<tachiyomi.domain.chapter.model.Chapter>? = null
private suspend fun getUnfilteredChapterList(): List<tachiyomi.domain.chapter.model.Chapter> {
if (unfilteredChapterListCache == null) {
val manga = manga!!
unfilteredChapterListCache = getChaptersByMangaId.await(manga.id, applyScanlatorFilter = false)
}
return unfilteredChapterListCache!!
private val unfilteredChapterList by lazy {
val manga = manga!!
runBlocking { getChaptersByMangaId.await(manga.id, applyScanlatorFilter = false) }
}
/**
* Chapter list for the active manga. It's retrieved lazily and should be accessed for the first
* time in a background thread to avoid blocking the UI.
*/
private var chapterListCache: List<ReaderChapter>? = null
private suspend fun getChapterList(): List<ReaderChapter> {
chapterListCache?.let { return it }
private val chapterList by lazy {
val manga = manga!!
// SY -->
val (chapters, mangaMap) =
val (chapters, mangaMap) = runBlocking {
if (manga.source == MERGED_SOURCE_ID) {
getMergedChaptersByMangaId.await(manga.id, applyScanlatorFilter = true) to
getMergedMangaById.await(manga.id)
@@ -209,7 +203,7 @@ class ReaderViewModel @JvmOverloads constructor(
} else {
getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true) to null
}
}
fun isChapterDownloaded(chapter: Chapter): Boolean {
val chapterManga = mangaMap?.get(chapter.mangaId) ?: manga
return downloadManager.isChapterDownloaded(
@@ -259,7 +253,7 @@ class ReaderViewModel @JvmOverloads constructor(
else -> chapters
}
val result = chaptersForReader
chaptersForReader
.sortedWith(getChapterSort(manga, sortDescending = false))
.run {
if (readerPreferences.skipDupe.get()) {
@@ -277,8 +271,6 @@ class ReaderViewModel @JvmOverloads constructor(
}
.map { it.toDbChapter() }
.map(::ReaderChapter)
chapterListCache = result
return result
}
private val incognitoMode: Boolean by lazy { getIncognitoState.await(manga?.source) }
@@ -417,7 +409,7 @@ class ReaderViewModel @JvmOverloads constructor(
loadChapter(
loader!!,
getChapterList().first { chapterId == it.chapter.id },
chapterList.first { chapterId == it.chapter.id },
/* SY --> */page, /* SY <-- */
)
Result.success(true)
@@ -435,10 +427,10 @@ class ReaderViewModel @JvmOverloads constructor(
}
// SY -->
suspend fun getChapters(): List<ReaderChapterItem> {
fun getChapters(): List<ReaderChapterItem> {
val currentChapter = getCurrentChapter()
return getChapterList().map {
return chapterList.map {
ReaderChapterItem(
chapter = it.chapter.toDomainChapter()!!,
manga = manga!!,
@@ -462,7 +454,6 @@ class ReaderViewModel @JvmOverloads constructor(
): ViewerChapters {
loader.loadChapter(chapter /* SY --> */, page/* SY <-- */)
val chapterList = getChapterList()
val chapterPos = chapterList.indexOf(chapter)
val newChapters = ViewerChapters(
chapter,
@@ -512,7 +503,7 @@ class ReaderViewModel @JvmOverloads constructor(
fun loadNewChapterFromDialog(chapter: Chapter) {
viewModelScope.launchIO {
val newChapter = getChapterList().firstOrNull { it.chapter.id == chapter.id } ?: return@launchIO
val newChapter = chapterList.firstOrNull { it.chapter.id == chapter.id } ?: return@launchIO
loadAdjacent(newChapter)
}
}
@@ -674,12 +665,11 @@ class ReaderViewModel @JvmOverloads constructor(
* If both conditions are satisfied enqueues chapter for delete
* @param currentChapter current chapter, which is going to be marked as read.
*/
private suspend fun deleteChapterIfNeeded(currentChapter: ReaderChapter) {
private fun deleteChapterIfNeeded(currentChapter: ReaderChapter) {
val removeAfterReadSlots = downloadPreferences.removeAfterReadSlots.get()
if (removeAfterReadSlots == -1) return
// Determine which chapter should be deleted and enqueue
val chapterList = getChapterList()
val currentChapterPosition = chapterList.indexOf(currentChapter)
val chapterToDelete = chapterList.getOrNull(currentChapterPosition - removeAfterReadSlots)
@@ -749,7 +739,7 @@ class ReaderViewModel @JvmOverloads constructor(
// SY -->
if (manga?.isEhBasedManga() == true) {
viewModelScope.launchNonCancellable {
val chapterUpdates = getUnfilteredChapterList()
val chapterUpdates = unfilteredChapterList
.filter { it.sourceOrder > readerChapter.chapter.source_order }
.map { chapter ->
ChapterUpdate(
@@ -769,7 +759,7 @@ class ReaderViewModel @JvmOverloads constructor(
.contains(LibraryPreferences.MARK_DUPLICATE_CHAPTER_READ_EXISTING)
if (!markDuplicateAsRead) return
val duplicateUnreadChapters = getUnfilteredChapterList()
val duplicateUnreadChapters = unfilteredChapterList
.mapNotNull { chapter ->
if (
!chapter.read &&
@@ -784,7 +774,7 @@ class ReaderViewModel @JvmOverloads constructor(
updateChapter.awaitAll(duplicateUnreadChapters)
// SY -->
duplicateUnreadChapters.forEach { chapterUpdate ->
val chapter = getUnfilteredChapterList().first { it.id == chapterUpdate.id }
val chapter = unfilteredChapterList.first { it.id == chapterUpdate.id }
deleteChapterIfNeeded(ReaderChapter(chapter))
}
// SY <--
@@ -873,9 +863,9 @@ class ReaderViewModel @JvmOverloads constructor(
// SY -->
fun toggleBookmark(chapterId: Long, bookmarked: Boolean) {
val chapter = chapterList.find { it.chapter.id == chapterId }?.chapter ?: return
chapter.bookmark = bookmarked
viewModelScope.launchNonCancellable {
val chapter = getChapterList().find { it.chapter.id == chapterId }?.chapter ?: return@launchNonCancellable
chapter.bookmark = bookmarked
updateChapter.await(
ChapterUpdate(
id = chapterId,
@@ -910,7 +900,7 @@ class ReaderViewModel @JvmOverloads constructor(
*/
fun setMangaReadingMode(readingMode: ReadingMode) {
val manga = manga ?: return
viewModelScope.launchIO {
runBlocking(Dispatchers.IO) {
setMangaViewerFlags.awaitSetReadingMode(manga.id, readingMode.flagValue.toLong())
val currChapters = state.value.viewerChapters
if (currChapters != null) {
@@ -251,7 +251,7 @@ class UpdatesScreenModel(
}
}
private suspend fun startDownloadingNow(chapterId: Long) {
private fun startDownloadingNow(chapterId: Long) {
downloadManager.startDownloadNow(chapterId)
}
@@ -35,7 +35,7 @@ object Migrator {
result = null
}
suspend fun awaitAndRelease(): Boolean {
return await().also { release() }
fun awaitAndRelease(): Boolean = runBlocking {
await().also { release() }
}
}
@@ -5,8 +5,6 @@ import kotlinx.coroutines.Job
import kotlinx.coroutines.asContextElement
import kotlinx.coroutines.runBlocking
import kotlinx.coroutines.suspendCancellableCoroutine
import kotlinx.coroutines.sync.Mutex
import kotlinx.coroutines.sync.withLock
import kotlinx.coroutines.withContext
import java.util.concurrent.RejectedExecutionException
import kotlin.concurrent.atomics.AtomicInt
@@ -19,10 +17,6 @@ import kotlin.coroutines.EmptyCoroutineContext
import kotlin.coroutines.coroutineContext
import kotlin.coroutines.resume
// Global mutex to serialize transaction entry and prevent thread pool exhaustion.
// If you have multiple distinct database files/handlers, this should be a property of AndroidDatabaseHandler.
private val transactionMutex = Mutex()
/**
* Returns the transaction dispatcher if we are on a transaction, or the database dispatchers.
*/
@@ -45,41 +39,20 @@ internal suspend fun AndroidDatabaseHandler.getCurrentDatabaseContext(): Corouti
* The dispatcher used to execute the given [block] will utilize threads from SQLDelight's query executor.
*/
internal suspend fun <T> AndroidDatabaseHandler.withTransaction(block: suspend () -> T): T {
val transactionElement = coroutineContext[TransactionElement]
// If we are already in a transaction, we don't need to lock the Mutex.
// We just reuse the existing thread/context.
if (transactionElement != null) {
return withContext(transactionElement.transactionDispatcher) {
transactionElement.acquire()
try {
db.transactionWithResult {
runBlocking(transactionElement.transactionDispatcher) {
block()
}
// Use inherited transaction context if available, this allows nested suspending transactions.
val transactionContext =
coroutineContext[TransactionElement]?.transactionDispatcher ?: createTransactionContext()
return withContext(transactionContext) {
val transactionElement = coroutineContext[TransactionElement]!!
transactionElement.acquire()
try {
db.transactionWithResult {
runBlocking(transactionContext) {
block()
}
} finally {
transactionElement.release()
}
}
}
// transaction: Acquire Mutex BEFORE acquiring a thread.
// This ensures we only block a real thread when we have exclusive access.
return transactionMutex.withLock {
val transactionContext = createTransactionContext()
withContext(transactionContext) {
val element = coroutineContext[TransactionElement]!!
element.acquire()
try {
db.transactionWithResult {
runBlocking(transactionContext) {
block()
}
}
} finally {
element.release()
}
} finally {
transactionElement.release()
}
}
}
@@ -170,7 +170,6 @@
<!-- Operations -->
<string name="loading">Loading…</string>
<string name="calculating">Calculating…</string>
<string name="internal_error">InternalError: Check crash logs for further information</string>
<!-- Shortcuts-->