From 912c340a01a05adb41b88ea8907b4085b290ca96 Mon Sep 17 00:00:00 2001 From: schroda <50052685+schroda@users.noreply.github.com> Date: Sun, 29 Oct 2023 16:01:55 +0100 Subject: [PATCH] Fix update subscription returning stale data (#727) In case a manga was already loaded via the data loader, the cached data will get used. Due to this, the update status did not return the updated manga data, but instead, stale data --- .../tachidesk/graphql/types/MangaType.kt | 18 ++++++++ .../tachidesk/graphql/types/UpdateType.kt | 45 ++++++++++++++++++- 2 files changed, 61 insertions(+), 2 deletions(-) diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt index d006f82ee..75858e3dd 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/MangaType.kt @@ -41,6 +41,24 @@ class MangaType( var lastFetchedAt: Long?, // todo var chaptersLastFetchedAt: Long?, // todo ) : Node { + companion object { + fun clearCacheFor( + mangaId: Int, + dataFetchingEnvironment: DataFetchingEnvironment, + ) { + dataFetchingEnvironment.getDataLoader, MangaNodeList>("MangaDataLoader").clear(listOf(mangaId)) + dataFetchingEnvironment.getDataLoader, MangaNodeList>("MangaForIdsDataLoader").clear(listOf(mangaId)) + dataFetchingEnvironment.getDataLoader("DownloadedChapterCountForMangaDataLoader").clear(mangaId) + dataFetchingEnvironment.getDataLoader("UnreadChapterCountForMangaDataLoader").clear(mangaId) + dataFetchingEnvironment.getDataLoader("LastReadChapterForMangaDataLoader").clear(mangaId) + dataFetchingEnvironment.getDataLoader( + "ChaptersForMangaDataLoader", + ).clear(mangaId) + dataFetchingEnvironment.getDataLoader("MangaMetaDataLoader").clear(mangaId) + dataFetchingEnvironment.getDataLoader("CategoriesForMangaDataLoader").clear(mangaId) + } + } + constructor(row: ResultRow) : this( row[MangaTable.id].value, row[MangaTable.sourceReference], diff --git a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/UpdateType.kt b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/UpdateType.kt index e91291d11..dbf691f26 100644 --- a/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/UpdateType.kt +++ b/server/src/main/kotlin/suwayomi/tachidesk/graphql/types/UpdateType.kt @@ -8,6 +8,8 @@ import suwayomi.tachidesk.manga.impl.update.JobStatus import suwayomi.tachidesk.manga.impl.update.UpdateStatus import java.util.concurrent.CompletableFuture +private val jobStatusToMangaIdsToCacheClearedStatus = mutableMapOf>() + class UpdateStatus( val isRunning: Boolean, val skippedCategories: UpdateStatusCategoryType, @@ -24,8 +26,22 @@ class UpdateStatus( updatingCategories = UpdateStatusCategoryType(status.categoryStatusMap[CategoryUpdateStatus.UPDATING]?.map { it.id }.orEmpty()), pendingJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.PENDING]?.map { it.id }.orEmpty()), runningJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.RUNNING]?.map { it.id }.orEmpty()), - completeJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.COMPLETE]?.map { it.id }.orEmpty()), - failedJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.FAILED]?.map { it.id }.orEmpty()), + completeJobs = + UpdateStatusType( + status.mangaStatusMap[JobStatus.COMPLETE]?.map { + it.id + }.orEmpty(), + JobStatus.COMPLETE, + status.running, + true, + ), + failedJobs = + UpdateStatusType( + status.mangaStatusMap[JobStatus.FAILED]?.map { it.id }.orEmpty(), + JobStatus.FAILED, + status.running, + true, + ), skippedJobs = UpdateStatusType(status.mangaStatusMap[JobStatus.SKIPPED]?.map { it.id }.orEmpty()), ) } @@ -42,8 +58,33 @@ class UpdateStatusCategoryType( class UpdateStatusType( @get:GraphQLIgnore val mangaIds: List, + private val jobStatus: JobStatus? = null, + private val isRunning: Boolean = false, + private val clearCache: Boolean = false, ) { fun mangas(dataFetchingEnvironment: DataFetchingEnvironment): CompletableFuture { + val resetClearedMangaIds = !isRunning && clearCache && jobStatus != null + if (resetClearedMangaIds) { + jobStatusToMangaIdsToCacheClearedStatus[jobStatus]?.clear() + } + + if (isRunning && clearCache && jobStatus != null) { + val cacheClearedForMangaIds = + jobStatusToMangaIdsToCacheClearedStatus.getOrPut( + jobStatus, + ) { emptyMap().toMutableMap() } + + mangaIds.forEach { + if (cacheClearedForMangaIds[it] == true) { + return@forEach + } + + MangaType.clearCacheFor(it, dataFetchingEnvironment) + + cacheClearedForMangaIds[it] = true + } + } + return dataFetchingEnvironment.getValueFromDataLoader, MangaNodeList>("MangaForIdsDataLoader", mangaIds) } }