From 4d565117cf83e1e3d3b73c2833a135040afcdd94 Mon Sep 17 00:00:00 2001 From: mramotar_dbx Date: Tue, 31 Oct 2023 12:43:12 -0400 Subject: [PATCH] Make StoreMultiCacheAccessor thread safe Signed-off-by: mramotar_dbx --- cache/build.gradle.kts | 1 + .../store/cache5/StoreMultiCacheAccessor.kt | 82 +++++++++++++++---- 2 files changed, 66 insertions(+), 17 deletions(-) diff --git a/cache/build.gradle.kts b/cache/build.gradle.kts index b5d635478..b13569c13 100644 --- a/cache/build.gradle.kts +++ b/cache/build.gradle.kts @@ -45,6 +45,7 @@ kotlin { dependencies { api(libs.kotlinx.atomic.fu) api(project(":core")) + implementation(libs.kotlinx.coroutines.core) } } val jvmMain by getting diff --git a/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/StoreMultiCacheAccessor.kt b/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/StoreMultiCacheAccessor.kt index 4d948c037..04e990569 100644 --- a/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/StoreMultiCacheAccessor.kt +++ b/cache/src/commonMain/kotlin/org/mobilenativefoundation/store/cache5/StoreMultiCacheAccessor.kt @@ -1,49 +1,87 @@ package org.mobilenativefoundation.store.cache5 +import kotlinx.atomicfu.locks.SynchronizedObject +import kotlinx.atomicfu.locks.synchronized import org.mobilenativefoundation.store.core5.StoreData import org.mobilenativefoundation.store.core5.StoreKey /** - * Intermediate data manager for a caching system supporting list decomposition. - * Tracks keys for rapid data retrieval and modification. + * Responsible for managing and accessing cached data. + * Provides functionality to retrieve, store, and invalidate single items and collections of items. + * All operations are thread-safe, ensuring safe usage across multiple threads. + * + * The thread safety of this class is ensured through the use of synchronized blocks. + * Synchronized blocks guarantee only one thread can execute any of the methods at a time. + * This prevents concurrent modifications and ensures consistency of the data. + * + * @param Id The type of the identifier used for the data. + * @param Collection The type of the data collection. + * @param Single The type of the single data item. + * @property singlesCache The cache used to store single data items. + * @property collectionsCache The cache used to store collections of data items. */ class StoreMultiCacheAccessor, Single : StoreData.Single>( private val singlesCache: Cache, Single>, private val collectionsCache: Cache, Collection>, -) { +) : SynchronizedObject() { private val keys = mutableSetOf>() - /** * Retrieves a collection of items from the cache using the provided key. + * + * This operation is thread-safe. + * + * @param key The key used to retrieve the collection. + * @return The cached collection or null if it's not present. */ - fun getCollection(key: StoreKey.Collection): Collection? = collectionsCache.getIfPresent(key) + fun getCollection(key: StoreKey.Collection): Collection? = synchronized(this) { + collectionsCache.getIfPresent(key) + } /** * Retrieves an individual item from the cache using the provided key. + * + * This operation is thread-safe. + * + * @param key The key used to retrieve the single item. + * @return The cached single item or null if it's not present. */ - fun getSingle(key: StoreKey.Single): Single? = singlesCache.getIfPresent(key) + fun getSingle(key: StoreKey.Single): Single? = synchronized(this) { + singlesCache.getIfPresent(key) + } /** * Stores a collection of items in the cache and updates the key set. + * + * This operation is thread-safe. + * + * @param key The key associated with the collection. + * @param collection The collection to be stored in the cache. */ - fun putCollection(key: StoreKey.Collection, collection: Collection) { + fun putCollection(key: StoreKey.Collection, collection: Collection) = synchronized(this) { collectionsCache.put(key, collection) keys.add(key) } /** * Stores an individual item in the cache and updates the key set. + * + * This operation is thread-safe. + * + * @param key The key associated with the single item. + * @param single The single item to be stored in the cache. */ - fun putSingle(key: StoreKey.Single, single: Single) { + fun putSingle(key: StoreKey.Single, single: Single) = synchronized(this) { singlesCache.put(key, single) keys.add(key) } /** * Removes all cache entries and clears the key set. + * + * This operation is thread-safe. */ - fun invalidateAll() { + fun invalidateAll() = synchronized(this) { collectionsCache.invalidateAll() singlesCache.invalidateAll() keys.clear() @@ -51,26 +89,36 @@ class StoreMultiCacheAccessor) { + fun invalidateSingle(key: StoreKey.Single) = synchronized(this) { singlesCache.invalidate(key) keys.remove(key) } /** * Removes a collection of items from the cache and updates the key set. + * + * This operation is thread-safe. + * + * @param key The key associated with the collection to be invalidated. */ - fun invalidateCollection(key: StoreKey.Collection) { + fun invalidateCollection(key: StoreKey.Collection) = synchronized(this) { collectionsCache.invalidate(key) keys.remove(key) } /** - * Calculates the total count of items in the cache. - * Includes individual items as well as items in collections. + * Calculates the total count of items in the cache, including both single items and items in collections. + * + * This operation is thread-safe. + * + * @return The total count of items in the cache. */ - - fun size(): Long { + fun size(): Long = synchronized(this) { var count = 0L for (key in keys) { when (key) { @@ -89,6 +137,6 @@ class StoreMultiCacheAccessor