Fix/gql download subscription errors spamming emits (#1518)

* Remove immediate download notification for latest gql subscription

There is a problem where too many immediate updates can cause the client to lag out (e.g., in case it has to update the queue in the cache based on the updates).
This happens in case e.g., a source is broken and all its downloads error out basically immediately.
With each errored out download, a new one starts, which causes an immediate notification to the clients.

* Determine downloader status from active state of downloader jobs

In case the downloader is active but all downloads are erroring out immediately, no download will have the DOWNLOADING status.
This then would result in the downloader status to constantly be STOPPED.

* Prevent multiple update for the same downloads

It was possible that multiple updates got added for the same download.
This caused issues with the graphql apollo client, because it wasn't able to correctly update the client cache.

* Set download error state only after reaching max retries

In case the max retries haven't been reached yet, the download will be retried and thus setting and emitting the error state will cause weird looking ui updates.
This commit is contained in:
schroda
2025-07-14 23:50:03 +02:00
committed by GitHub
parent e7e76ed68d
commit 09c950a890
2 changed files with 34 additions and 13 deletions

View File

@@ -31,7 +31,6 @@ import org.jetbrains.exposed.sql.and
import org.jetbrains.exposed.sql.selectAll
import org.jetbrains.exposed.sql.transactions.transaction
import suwayomi.tachidesk.manga.impl.download.model.DownloadChapter
import suwayomi.tachidesk.manga.impl.download.model.DownloadState.Downloading
import suwayomi.tachidesk.manga.impl.download.model.DownloadState.Error
import suwayomi.tachidesk.manga.impl.download.model.DownloadState.Queued
import suwayomi.tachidesk.manga.impl.download.model.DownloadStatus
@@ -147,7 +146,7 @@ object DownloadManager {
init {
scope.launch {
notifyFlow.sample(1.seconds).collect {
notifyAllClients(immediate = true)
notifyAllClients(immediate = true, gqlEmit = true)
}
}
}
@@ -167,18 +166,37 @@ object DownloadManager {
private fun notifyAllClients(
immediate: Boolean = false,
downloads: List<DownloadUpdate> = emptyList(),
gqlEmit: Boolean = false,
) {
downloadUpdates.removeAll { update ->
downloads.any { download ->
download.downloadChapter.chapter.id ==
update.downloadChapter.chapter.id
}
}
downloadUpdates.addAll(downloads)
if (immediate) {
val status = getStatus()
// There is a problem where too many immediate updates can cause the client to lag out (e.g., in case it has to
// update the queue in the cache based on the updates).
// This happens in case e.g., a source is broken and all its downloads error out basically immediately.
// With each errored out download, a new one starts, which causes an immediate notification to the clients.
// While the immediate notification functionality is no longer needed for the latest graphql download subscription,
// it is still required for the deprecated version as well as the rest api subscription.
if (gqlEmit) {
val updates = getDownloadUpdates()
downloadUpdates.clear()
scope.launch {
statusFlow.emit(status)
updatesFlow.emit(updates)
}
}
if (immediate) {
val status = getStatus()
scope.launch {
statusFlow.emit(status)
sendStatusToAllClients(status)
}
@@ -192,20 +210,20 @@ object DownloadManager {
fun getStatus(): DownloadStatus =
DownloadStatus(
if (downloadQueue.none { it.state == Downloading }) {
Status.Stopped
} else {
if (downloaders.values.any { it.isActive }) {
Status.Started
} else {
Status.Stopped
},
downloadQueue.toList(),
)
private fun getDownloadUpdates(addInitial: Boolean = false): DownloadUpdates =
DownloadUpdates(
if (downloadQueue.none { it.state == Downloading }) {
Status.Stopped
} else {
if (downloaders.values.any { it.isActive }) {
Status.Started
} else {
Status.Stopped
},
downloadUpdates.toList(),
if (addInitial) downloadQueue.toList() else null,

View File

@@ -171,8 +171,11 @@ class Downloader(
} catch (e: Exception) {
downloadLogger.warn(e) { "failed due to" }
download.tries++
download.state = Error
notifier(false, DownloadUpdate(ERROR, download))
download.state = Queued
if (download.tries >= MAX_RETRIES) {
download.state = Error
notifier(false, DownloadUpdate(ERROR, download))
}
}
}
}