diff --git a/CHANGELOG.md b/CHANGELOG.md index ee907d038..65d279bca 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -39,6 +39,7 @@ The format is a modified version of [Keep a Changelog](https://keepachangelog.co - Fixed refreshing chapters failing with the app language set to Arabic or another language with its own digits ([@AntsyLich](https://github.com/AntsyLich)) - Fixed a crash on launch on some Samsung devices running Android 9 to 11 ([@AntsyLich](https://github.com/AntsyLich)) - Fixed the start and resume button going under the taskbar on tablets ([@AntsyLich](https://github.com/AntsyLich)) +- Fixed a crash when updating an extension while others were installing ([@AntsyLich](https://github.com/AntsyLich)) ## [v0.21.0] - 2026-10-12 ### Added diff --git a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt index 3900806b2..134d2edab 100644 --- a/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt +++ b/app/src/main/java/eu/kanade/tachiyomi/extension/installer/Installer.kt @@ -22,6 +22,8 @@ abstract class Installer(private val service: Service) { private val extensionManager: ExtensionManager by lazy { service.appGraph.extensionManager } private var waitingInstall = AtomicReference(null) + + // Guarded by itself: cancellation comes from any thread, not only main private val queue = Collections.synchronizedList(mutableListOf()) private val cancelListener: (Long) -> Unit = { downloadId -> cancelQueue(downloadId) } @@ -91,15 +93,15 @@ abstract class Installer(private val service: Service) { if (!ready) { return } - if (queue.isEmpty()) { - service.stopSelf() - return - } - val nextEntry = queue.first() - if (waitingInstall.compareAndSet(null, nextEntry)) { + val nextEntry = synchronized(queue) { + if (queue.isEmpty()) { + service.stopSelf() + return + } + if (!waitingInstall.compareAndSet(null, queue.first())) return queue.removeAt(0) - processEntry(nextEntry) } + processEntry(nextEntry) } /** @@ -108,11 +110,11 @@ abstract class Installer(private val service: Service) { @CallSuper open fun onDestroy() { cancelListeners -= cancelListener - queue.forEach { + val remaining = synchronized(queue) { queue.toList().also { queue.clear() } } + remaining.forEach { logcat(LogPriority.INFO) { "The installer stopped before installing ${it.downloadId}" } extensionManager.updateInstallStep(it.downloadId, InstallStep.Error) } - queue.clear() waitingInstall.store(null) } @@ -125,7 +127,8 @@ abstract class Installer(private val service: Service) { */ private fun cancelQueue(downloadId: Long) { val waitingInstall = this.waitingInstall.load() - val toCancel = queue.find { it.downloadId == downloadId } ?: waitingInstall ?: return + val toCancel = synchronized(queue) { queue.find { it.downloadId == downloadId } } + ?: waitingInstall ?: return if (cancelEntry(toCancel)) { queue.remove(toCancel) if (waitingInstall == toCancel) {