diff --git a/normalized-cache-incubating/api/normalized-cache-incubating.api b/normalized-cache-incubating/api/normalized-cache-incubating.api index e1a9b19..7b3b147 100644 --- a/normalized-cache-incubating/api/normalized-cache-incubating.api +++ b/normalized-cache-incubating/api/normalized-cache-incubating.api @@ -107,7 +107,7 @@ public final class com/apollographql/cache/normalized/GarbageCollectionKt { public static final fun garbageCollect-SxA4cEA (Lcom/apollographql/cache/normalized/api/NormalizedCache;Lcom/apollographql/cache/normalized/api/MaxAgeProvider;J)Lcom/apollographql/cache/normalized/GarbageCollectResult; public static synthetic fun garbageCollect-SxA4cEA$default (Lcom/apollographql/cache/normalized/ApolloStore;Lcom/apollographql/cache/normalized/api/MaxAgeProvider;JILjava/lang/Object;)Lcom/apollographql/cache/normalized/GarbageCollectResult; public static synthetic fun garbageCollect-SxA4cEA$default (Lcom/apollographql/cache/normalized/api/NormalizedCache;Lcom/apollographql/cache/normalized/api/MaxAgeProvider;JILjava/lang/Object;)Lcom/apollographql/cache/normalized/GarbageCollectResult; - public static final fun getReachableCacheKeys (Lcom/apollographql/cache/normalized/api/NormalizedCache;)Ljava/util/Set; + public static final fun getReachableCacheKeys (Ljava/util/Map;)Ljava/util/Set; public static final fun removeDanglingReferences (Lcom/apollographql/cache/normalized/ApolloStore;)Lcom/apollographql/cache/normalized/RemovedFieldsAndRecords; public static final fun removeDanglingReferences (Lcom/apollographql/cache/normalized/api/NormalizedCache;)Lcom/apollographql/cache/normalized/RemovedFieldsAndRecords; public static final fun removeStaleFields-SxA4cEA (Lcom/apollographql/cache/normalized/ApolloStore;Lcom/apollographql/cache/normalized/api/MaxAgeProvider;J)Lcom/apollographql/cache/normalized/RemovedFieldsAndRecords; diff --git a/normalized-cache-incubating/api/normalized-cache-incubating.klib.api b/normalized-cache-incubating/api/normalized-cache-incubating.klib.api index ee171bc..9ce7640 100644 --- a/normalized-cache-incubating/api/normalized-cache-incubating.klib.api +++ b/normalized-cache-incubating/api/normalized-cache-incubating.klib.api @@ -360,7 +360,6 @@ final fun (com.apollographql.apollo/ApolloClient.Builder).com.apollographql.cach final fun (com.apollographql.apollo/ApolloClient.Builder).com.apollographql.cache.normalized/store(com.apollographql.cache.normalized/ApolloStore, kotlin/Boolean = ...): com.apollographql.apollo/ApolloClient.Builder // com.apollographql.cache.normalized/store|store@com.apollographql.apollo.ApolloClient.Builder(com.apollographql.cache.normalized.ApolloStore;kotlin.Boolean){}[0] final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/allRecords(): kotlin.collections/Map // com.apollographql.cache.normalized/allRecords|allRecords@com.apollographql.cache.normalized.api.NormalizedCache(){}[0] final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/garbageCollect(com.apollographql.cache.normalized.api/MaxAgeProvider, kotlin.time/Duration = ...): com.apollographql.cache.normalized/GarbageCollectResult // com.apollographql.cache.normalized/garbageCollect|garbageCollect@com.apollographql.cache.normalized.api.NormalizedCache(com.apollographql.cache.normalized.api.MaxAgeProvider;kotlin.time.Duration){}[0] -final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/getReachableCacheKeys(): kotlin.collections/Set // com.apollographql.cache.normalized/getReachableCacheKeys|getReachableCacheKeys@com.apollographql.cache.normalized.api.NormalizedCache(){}[0] final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/removeDanglingReferences(): com.apollographql.cache.normalized/RemovedFieldsAndRecords // com.apollographql.cache.normalized/removeDanglingReferences|removeDanglingReferences@com.apollographql.cache.normalized.api.NormalizedCache(){}[0] final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/removeStaleFields(com.apollographql.cache.normalized.api/MaxAgeProvider, kotlin.time/Duration = ...): com.apollographql.cache.normalized/RemovedFieldsAndRecords // com.apollographql.cache.normalized/removeStaleFields|removeStaleFields@com.apollographql.cache.normalized.api.NormalizedCache(com.apollographql.cache.normalized.api.MaxAgeProvider;kotlin.time.Duration){}[0] final fun (com.apollographql.cache.normalized.api/NormalizedCache).com.apollographql.cache.normalized/removeUnreachableRecords(): kotlin.collections/Set // com.apollographql.cache.normalized/removeUnreachableRecords|removeUnreachableRecords@com.apollographql.cache.normalized.api.NormalizedCache(){}[0] @@ -372,6 +371,7 @@ final fun (com.apollographql.cache.normalized/ApolloStore).com.apollographql.cac final fun (com.apollographql.cache.normalized/ApolloStore).com.apollographql.cache.normalized/removeStaleFields(com.apollographql.cache.normalized.api/MaxAgeProvider, kotlin.time/Duration = ...): com.apollographql.cache.normalized/RemovedFieldsAndRecords // com.apollographql.cache.normalized/removeStaleFields|removeStaleFields@com.apollographql.cache.normalized.ApolloStore(com.apollographql.cache.normalized.api.MaxAgeProvider;kotlin.time.Duration){}[0] final fun (com.apollographql.cache.normalized/ApolloStore).com.apollographql.cache.normalized/removeUnreachableRecords(): kotlin.collections/Set // com.apollographql.cache.normalized/removeUnreachableRecords|removeUnreachableRecords@com.apollographql.cache.normalized.ApolloStore(){}[0] final fun (kotlin.collections/Collection?).com.apollographql.cache.normalized.api/dependentKeys(): kotlin.collections/Set // com.apollographql.cache.normalized.api/dependentKeys|dependentKeys@kotlin.collections.Collection?(){}[0] +final fun (kotlin.collections/Map).com.apollographql.cache.normalized/getReachableCacheKeys(): kotlin.collections/Set // com.apollographql.cache.normalized/getReachableCacheKeys|getReachableCacheKeys@kotlin.collections.Map(){}[0] final fun <#A: com.apollographql.apollo.api/Executable.Data> (com.apollographql.apollo.api/Executable<#A>).com.apollographql.cache.normalized.api/normalize(#A, com.apollographql.apollo.api/CustomScalarAdapters, com.apollographql.cache.normalized.api/CacheKeyGenerator, com.apollographql.cache.normalized.api/MetadataGenerator = ..., com.apollographql.cache.normalized.api/FieldKeyGenerator = ..., com.apollographql.cache.normalized.api/EmbeddedFieldsProvider = ..., kotlin/String): kotlin.collections/Map // com.apollographql.cache.normalized.api/normalize|normalize@com.apollographql.apollo.api.Executable<0:0>(0:0;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.CacheKeyGenerator;com.apollographql.cache.normalized.api.MetadataGenerator;com.apollographql.cache.normalized.api.FieldKeyGenerator;com.apollographql.cache.normalized.api.EmbeddedFieldsProvider;kotlin.String){0§}[0] final fun <#A: com.apollographql.apollo.api/Executable.Data> (com.apollographql.apollo.api/Executable<#A>).com.apollographql.cache.normalized.api/readDataFromCache(com.apollographql.apollo.api/CustomScalarAdapters, com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache, com.apollographql.cache.normalized.api/CacheResolver, com.apollographql.cache.normalized.api/CacheHeaders, com.apollographql.cache.normalized.api/FieldKeyGenerator = ...): #A // com.apollographql.cache.normalized.api/readDataFromCache|readDataFromCache@com.apollographql.apollo.api.Executable<0:0>(com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.ReadOnlyNormalizedCache;com.apollographql.cache.normalized.api.CacheResolver;com.apollographql.cache.normalized.api.CacheHeaders;com.apollographql.cache.normalized.api.FieldKeyGenerator){0§}[0] final fun <#A: com.apollographql.apollo.api/Executable.Data> (com.apollographql.apollo.api/Executable<#A>).com.apollographql.cache.normalized.api/readDataFromCache(com.apollographql.cache.normalized.api/CacheKey, com.apollographql.apollo.api/CustomScalarAdapters, com.apollographql.cache.normalized.api/ReadOnlyNormalizedCache, com.apollographql.cache.normalized.api/CacheResolver, com.apollographql.cache.normalized.api/CacheHeaders, com.apollographql.cache.normalized.api/FieldKeyGenerator = ...): #A // com.apollographql.cache.normalized.api/readDataFromCache|readDataFromCache@com.apollographql.apollo.api.Executable<0:0>(com.apollographql.cache.normalized.api.CacheKey;com.apollographql.apollo.api.CustomScalarAdapters;com.apollographql.cache.normalized.api.ReadOnlyNormalizedCache;com.apollographql.cache.normalized.api.CacheResolver;com.apollographql.cache.normalized.api.CacheHeaders;com.apollographql.cache.normalized.api.FieldKeyGenerator){0§}[0] diff --git a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/GarbageCollection.kt b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/GarbageCollection.kt index 354b9eb..20b0372 100644 --- a/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/GarbageCollection.kt +++ b/normalized-cache-incubating/src/commonMain/kotlin/com/apollographql/cache/normalized/GarbageCollection.kt @@ -15,12 +15,12 @@ import com.apollographql.cache.normalized.api.receivedDate import kotlin.time.Duration @ApolloInternal -fun NormalizedCache.getReachableCacheKeys(): Set { - fun NormalizedCache.getReachableCacheKeys(roots: List, reachableCacheKeys: MutableSet) { - val records = loadRecords(roots.map { it.key }, CacheHeaders.NONE).associateBy { it.key } +fun Map.getReachableCacheKeys(): Set { + fun Map.getReachableCacheKeys(roots: List, reachableCacheKeys: MutableSet) { + val records = roots.mapNotNull { this[it.key] } val cacheKeysToCheck = mutableListOf() - for ((key, record) in records) { - reachableCacheKeys.add(CacheKey(key)) + for (record in records) { + reachableCacheKeys.add(CacheKey(record.key)) cacheKeysToCheck.addAll(record.referencedFields() - reachableCacheKeys) } if (cacheKeysToCheck.isNotEmpty()) { @@ -45,7 +45,12 @@ fun NormalizedCache.allRecords(): Map { * @return the cache keys that were removed. */ fun NormalizedCache.removeUnreachableRecords(): Set { - val unreachableCacheKeys = allRecords().keys.map { CacheKey(it) } - getReachableCacheKeys() + val allRecords = allRecords() + return removeUnreachableRecords(allRecords) +} + +private fun NormalizedCache.removeUnreachableRecords(allRecords: Map): Set { + val unreachableCacheKeys = allRecords.keys.map { CacheKey(it) } - allRecords.getReachableCacheKeys() remove(unreachableCacheKeys, cascade = false) return unreachableCacheKeys.toSet() } @@ -79,10 +84,18 @@ fun NormalizedCache.removeStaleFields( maxAgeProvider: MaxAgeProvider, maxStale: Duration = Duration.ZERO, ): RemovedFieldsAndRecords { - val allRecords: Map = allRecords() + val allRecords = allRecords().toMutableMap() + return removeStaleFields(allRecords, maxAgeProvider, maxStale) +} + +private fun NormalizedCache.removeStaleFields( + allRecords: MutableMap, + maxAgeProvider: MaxAgeProvider, + maxStale: Duration, +): RemovedFieldsAndRecords { val recordsToUpdate = mutableMapOf() val removedFields = mutableSetOf() - for (record in allRecords.values) { + for (record in allRecords.values.toList()) { var recordCopy = record for (field in record.fields) { // Consider the client controlled max age @@ -103,6 +116,11 @@ fun NormalizedCache.removeStaleFields( recordCopy -= field.key recordsToUpdate[record.key] = recordCopy removedFields.add(record.key + "." + field.key) + if (recordCopy.isEmptyRecord()) { + allRecords.remove(record.key) + } else { + allRecords[record.key] = recordCopy + } continue } } @@ -116,6 +134,11 @@ fun NormalizedCache.removeStaleFields( recordCopy -= field.key recordsToUpdate[record.key] = recordCopy removedFields.add(record.key + "." + field.key) + if (recordCopy.isEmptyRecord()) { + allRecords.remove(record.key) + } else { + allRecords[record.key] = recordCopy + } } } } @@ -160,6 +183,10 @@ fun ApolloStore.removeStaleFields( */ fun NormalizedCache.removeDanglingReferences(): RemovedFieldsAndRecords { val allRecords: MutableMap = allRecords().toMutableMap() + return removeDanglingReferences(allRecords) +} + +private fun NormalizedCache.removeDanglingReferences(allRecords: MutableMap): RemovedFieldsAndRecords { val recordsToUpdate = mutableMapOf() val allRemovedFields = mutableSetOf() do { @@ -254,10 +281,11 @@ fun NormalizedCache.garbageCollect( maxAgeProvider: MaxAgeProvider, maxStale: Duration = Duration.ZERO, ): GarbageCollectResult { + val allRecords = allRecords().toMutableMap() return GarbageCollectResult( - removedStaleFields = removeStaleFields(maxAgeProvider, maxStale), - removedDanglingReferences = removeDanglingReferences(), - removedUnreachableRecords = removeUnreachableRecords() + removedStaleFields = removeStaleFields(allRecords, maxAgeProvider, maxStale), + removedDanglingReferences = removeDanglingReferences(allRecords), + removedUnreachableRecords = removeUnreachableRecords(allRecords) ) } diff --git a/tests/garbage-collection/src/commonTest/kotlin/ReachableCacheKeysTest.kt b/tests/garbage-collection/src/commonTest/kotlin/ReachableCacheKeysTest.kt index 77216fa..2e0a1a6 100644 --- a/tests/garbage-collection/src/commonTest/kotlin/ReachableCacheKeysTest.kt +++ b/tests/garbage-collection/src/commonTest/kotlin/ReachableCacheKeysTest.kt @@ -126,7 +126,7 @@ class ReachableCacheKeysTest { ) apolloClient.query(query).fetchPolicy(FetchPolicy.NetworkOnly).execute() - var reachableCacheKeys = store.accessCache { it.getReachableCacheKeys() } + var reachableCacheKeys = store.accessCache { it.allRecords().getReachableCacheKeys() } assertContentEquals( listOf( CacheKey("QUERY_ROOT"), @@ -147,7 +147,7 @@ class ReachableCacheKeysTest { // Remove User 43, now Repositories 5 and 6 should not be reachable / 7 should still be reachable store.remove(CacheKey("User:43"), cascade = false) - reachableCacheKeys = store.accessCache { it.getReachableCacheKeys() } + reachableCacheKeys = store.accessCache { it.allRecords().getReachableCacheKeys() } assertContentEquals( listOf( CacheKey("QUERY_ROOT"), @@ -169,7 +169,7 @@ class ReachableCacheKeysTest { CacheKey("Repository:500"), RepositoryFragment(id = "500", __typename = "Repository", starGazers = emptyList()), ) - reachableCacheKeys = store.accessCache { it.getReachableCacheKeys() } + reachableCacheKeys = store.accessCache { it.allRecords().getReachableCacheKeys() } assertContentEquals( listOf( CacheKey("QUERY_ROOT"),