From d3dcb7fe4ba707fc8f4236a84083beedea8ca4ce Mon Sep 17 00:00:00 2001 From: AntsyLich <59261191+AntsyLich@users.noreply.github.com> Date: Fri, 20 Mar 2026 15:28:29 +0600 Subject: [PATCH] Revert "Fix thread starvation caused by not yielding or using an inappropriate thread pool (#2955)" This reverts commit 22d5c9d9f4995598e1385f8369836b86125e6f81. --- CHANGELOG.md | 3 -- .../settings/screen/SettingsDataScreen.kt | 5 +- .../more/settings/screen/data/StorageInfo.kt | 44 ++++------------ .../tachiyomi/data/cache/ChapterCache.kt | 14 ++--- .../data/download/DownloadManager.kt | 4 +- .../tachiyomi/data/download/DownloadStore.kt | 6 +-- .../tachiyomi/data/download/Downloader.kt | 6 +-- .../data/notification/NotificationReceiver.kt | 38 +++++--------- .../tachiyomi/extension/ExtensionManager.kt | 19 +++---- .../extension/util/ExtensionLoader.kt | 10 ++-- .../kanade/tachiyomi/ui/main/MainActivity.kt | 9 ++-- .../tachiyomi/ui/reader/ReaderViewModel.kt | 31 ++++------- .../ui/updates/UpdatesScreenModel.kt | 2 +- .../java/mihon/core/migration/Migrator.kt | 4 +- .../java/tachiyomi/data/TransactionContext.kt | 51 +++++-------------- .../moko-resources/base/strings.xml | 1 - 16 files changed, 83 insertions(+), 164 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 74d95911e..47fcc20c1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -14,9 +14,6 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co ### Changed - Retry in reader now redownloads image ([@AntsyLich](https://github.com/AntsyLich)) ([#3089](https://github.com/mihonapp/mihon/pull/3089)) -### Improved -- Rework internals for better performance ([@Lolle2000la](https://github.com/Lolle2000la)) ([#2955](https://github.com/mihonapp/mihon/pull/2955)) - ### Fixed - Fix performance regression introduced in v0.19.4 ([@AntsyLich](https://github.com/AntsyLich)) ([#3082](https://github.com/mihonapp/mihon/pull/3082)) - Fix duplicate key crash in duplicate detection ([@leodyversemilla07](https://github.com/leodyversemilla07)) ([#3040](https://github.com/mihonapp/mihon/pull/3040)) diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt index 0d541d3ed..d90aac40d 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/SettingsDataScreen.kt @@ -285,10 +285,7 @@ object SettingsDataScreen : SearchableSettings { val chapterCache = remember { Injekt.get() } 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 } return Preference.PreferenceGroup( title = stringResource(MR.strings.pref_storage_usage), diff --git a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/StorageInfo.kt b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/StorageInfo.kt index a8578d97d..5fed6c6ef 100644 --- a/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/StorageInfo.kt +++ b/app/src/main/java/eu/kanade/presentation/more/settings/screen/data/StorageInfo.kt @@ -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), diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt index 2c3b8e336..1d47f5b7d 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/cache/ChapterCache.kt @@ -6,8 +6,6 @@ import com.jakewharton.disklrucache.DiskLruCache import eu.kanade.tachiyomi.source.model.Page import eu.kanade.tachiyomi.util.storage.DiskUtil import eu.kanade.tachiyomi.util.storage.saveTo -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext import kotlinx.serialization.json.Json import logcat.LogPriority import okhttp3.Response @@ -44,13 +42,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) /** * Get page list from cache. diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt index 2a57f76f9..8255b22c8 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadManager.kt @@ -104,10 +104,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) diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt index a804fcf73..3d95909bf 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/DownloadStore.kt @@ -90,7 +90,7 @@ class DownloadStore( /** * Returns the list of downloads to restore. It should be called in a background thread. */ - suspend fun restore(): List { + fun restore(): List { val objs = preferences.all .mapNotNull { it.value as? String } .mapNotNull { deserialize(it) } @@ -101,10 +101,10 @@ class DownloadStore( val cachedManga = mutableMapOf() 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)) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt index 521b3a75d..60134351a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/download/Downloader.kt @@ -111,9 +111,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()) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt index 05615ad4f..e23b3f78c 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/data/notification/NotificationReceiver.kt @@ -20,7 +20,6 @@ import eu.kanade.tachiyomi.util.system.toast 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 @@ -79,18 +78,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 -> { @@ -155,18 +147,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) } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt index 31132c93d..a63105623 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/ExtensionManager.kt @@ -23,7 +23,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 @@ -119,19 +118,17 @@ 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() - .associate { it.extension.pkgName to it.extension } + installedExtensionMapFlow.value = extensions + .filterIsInstance() + .associate { it.extension.pkgName to it.extension } - untrustedExtensionMapFlow.value = extensions - .filterIsInstance() - .associate { it.extension.pkgName to it.extension } + untrustedExtensionMapFlow.value = extensions + .filterIsInstance() + .associate { it.extension.pkgName to it.extension } - _isInitialized.value = true - } + _isInitialized.value = true } /** diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt index 02f150d8b..db6b0aa40 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/util/ExtensionLoader.kt @@ -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 { + fun loadExtensions(context: Context): List { 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() } } diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt index a89e347cd..3c1beda74 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/main/MainActivity.kt @@ -131,6 +131,8 @@ class MainActivity : BaseActivity() { super.onCreate(savedInstanceState) + val didMigration = Migrator.awaitAndRelease() + // Do not let the launcher create a new activity http://stackoverflow.com/questions/16283079 if (!isTaskRoot) { finish() @@ -138,11 +140,6 @@ class MainActivity : BaseActivity() { } setComposeContent { - var didMigration by remember { mutableStateOf(null) } - LaunchedEffect(Unit) { - didMigration = Migrator.awaitAndRelease() - } - val context = LocalContext.current var incognito by remember { mutableStateOf(getIncognitoState.await(null)) } @@ -245,7 +242,7 @@ class MainActivity : BaseActivity() { ShowOnboarding() } - var showChangelog by remember { mutableStateOf(didMigration == true && !BuildConfig.DEBUG) } + var showChangelog by remember { mutableStateOf(didMigration && !BuildConfig.DEBUG) } if (showChangelog) { AlertDialog( onDismissRequest = { showChangelog = false }, diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt index 54ca8b57f..4c227636a 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/reader/ReaderViewModel.kt @@ -145,25 +145,18 @@ class ReaderViewModel @JvmOverloads constructor( private var chapterToDownload: Download? = null - private var unfilteredChapterListCache: List? = null - private suspend fun getUnfilteredChapterList(): List { - 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? = null - private suspend fun getChapterList(): List { - chapterListCache?.let { return it } - + private val chapterList by lazy { val manga = manga!! - val chapters = getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true) + val chapters = runBlocking { getChaptersByMangaId.await(manga.id, applyScanlatorFilter = true) } val selectedChapter = chapters.find { it.id == chapterId } ?: error("Requested chapter of id $chapterId not found in chapter list") @@ -212,7 +205,7 @@ class ReaderViewModel @JvmOverloads constructor( else -> chapters } - val result = chaptersForReader + chaptersForReader .sortedWith(getChapterSort(manga, sortDescending = false)) .run { if (readerPreferences.skipDupe.get()) { @@ -230,8 +223,6 @@ class ReaderViewModel @JvmOverloads constructor( } .map { it.toDbChapter() } .map(::ReaderChapter) - chapterListCache = result - return result } private val incognitoMode: Boolean by lazy { getIncognitoState.await(manga?.source) } @@ -297,7 +288,7 @@ class ReaderViewModel @JvmOverloads constructor( val source = sourceManager.getOrStub(manga.source) loader = ChapterLoader(context, downloadManager, downloadProvider, manga, source) - loadChapter(loader!!, getChapterList().first { chapterId == it.chapter.id }) + loadChapter(loader!!, chapterList.first { chapterId == it.chapter.id }) Result.success(true) } else { // Unlikely but okay @@ -322,7 +313,6 @@ class ReaderViewModel @JvmOverloads constructor( ): ViewerChapters { loader.loadChapter(chapter) - val chapterList = getChapterList() val chapterPos = chapterList.indexOf(chapter) val newChapters = ViewerChapters( chapter, @@ -521,12 +511,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) @@ -577,7 +566,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 && @@ -690,7 +679,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) { diff --git a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt index 8e86d1716..943d34b30 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/ui/updates/UpdatesScreenModel.kt @@ -239,7 +239,7 @@ class UpdatesScreenModel( } } - private suspend fun startDownloadingNow(chapterId: Long) { + private fun startDownloadingNow(chapterId: Long) { downloadManager.startDownloadNow(chapterId) } diff --git a/app/src/main/java/mihon/core/migration/Migrator.kt b/app/src/main/java/mihon/core/migration/Migrator.kt index 2168d58a1..c01a3873e 100644 --- a/app/src/main/java/mihon/core/migration/Migrator.kt +++ b/app/src/main/java/mihon/core/migration/Migrator.kt @@ -35,7 +35,7 @@ object Migrator { result = null } - suspend fun awaitAndRelease(): Boolean { - return await().also { release() } + fun awaitAndRelease(): Boolean = runBlocking { + await().also { release() } } } diff --git a/data/src/main/java/tachiyomi/data/TransactionContext.kt b/data/src/main/java/tachiyomi/data/TransactionContext.kt index 77154696b..f804a0b8e 100644 --- a/data/src/main/java/tachiyomi/data/TransactionContext.kt +++ b/data/src/main/java/tachiyomi/data/TransactionContext.kt @@ -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 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() } } } diff --git a/i18n/src/commonMain/moko-resources/base/strings.xml b/i18n/src/commonMain/moko-resources/base/strings.xml index aea867ed5..4e9d27475 100644 --- a/i18n/src/commonMain/moko-resources/base/strings.xml +++ b/i18n/src/commonMain/moko-resources/base/strings.xml @@ -171,7 +171,6 @@ Loading… - Calculating… InternalError: Check crash logs for further information