From 6dbab363d01e9c0c2e2cddc62db73957ff17a503 Mon Sep 17 00:00:00 2001 From: Milan Date: Mon, 19 May 2025 10:42:00 +0200 Subject: [PATCH] Shared preferences object cached in-memory and locked with semaphore (#1874) * Shared preferences object cached in-memory and locked with semaphore * Documentation update --- CHANGELOG.md | 3 ++ .../preference/AndroidPreferenceProvider.kt | 52 ++++++++++++------- 2 files changed, 36 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 2d131cbd9..8fe7bc562 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -6,6 +6,9 @@ and this application adheres to [Semantic Versioning](https://semver.org/spec/v2 ## [Unreleased] +### Fixed +- Shared preferences object cached in-memory and locked with semaphore in order to improve stability of security-crypto library + ## [2.0.2 (962)] - 2025-05-14 ### Fixed diff --git a/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/AndroidPreferenceProvider.kt b/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/AndroidPreferenceProvider.kt index b4ed07698..e25748471 100644 --- a/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/AndroidPreferenceProvider.kt +++ b/preference-impl-android-lib/src/main/java/co/electriccoin/zcash/preference/AndroidPreferenceProvider.kt @@ -30,7 +30,7 @@ import java.util.concurrent.Executors * this instance lives for the lifetime of the application. Constructing multiple instances will * potentially corrupt preference data and will leak resources. */ -class AndroidPreferenceProvider private constructor( +class AndroidPreferenceProvider( private val sharedPreferences: SharedPreferences, private val dispatcher: CoroutineDispatcher ) : PreferenceProvider { @@ -163,15 +163,26 @@ class AndroidPreferenceProvider private constructor( } } - companion object { - suspend fun newStandard( - context: Context, - filename: String - ): PreferenceProvider { - /* - * Because of this line, we don't want multiple instances of this object created - * because we don't clean up the thread afterwards. - */ + companion object Factory : AndroidPreferenceFactory by AndroidPreferenceFactoryImpl() +} + +interface AndroidPreferenceFactory { + suspend fun newStandard(context: Context, filename: String): PreferenceProvider + + suspend fun newEncrypted(context: Context, filename: String): PreferenceProvider +} + +private class AndroidPreferenceFactoryImpl : AndroidPreferenceFactory { + private val semaphore = Mutex() + private val standardCache = mutableMapOf() + private val encryptedCache = mutableMapOf() + + override suspend fun newStandard(context: Context, filename: String) = + getOrCreate(standardCache, filename) { + /* + * Because of this line, we don't want multiple instances of this object created + * because we don't clean up the thread afterwards. + */ val singleThreadedDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() val sharedPreferences = @@ -182,14 +193,12 @@ class AndroidPreferenceProvider private constructor( return AndroidPreferenceProvider(sharedPreferences, singleThreadedDispatcher) } - suspend fun newEncrypted( - context: Context, - filename: String - ): PreferenceProvider { - /* - * Because of this line, we don't want multiple instances of this object created - * because we don't clean up the thread afterwards. - */ + override suspend fun newEncrypted(context: Context, filename: String) = + getOrCreate(encryptedCache, filename) { + /* + * Because of this line, we don't want multiple instances of this object created + * because we don't clean up the thread afterwards. + */ val singleThreadedDispatcher = Executors.newSingleThreadExecutor().asCoroutineDispatcher() val sharedPreferences = @@ -212,5 +221,10 @@ class AndroidPreferenceProvider private constructor( return AndroidPreferenceProvider(sharedPreferences, singleThreadedDispatcher) } - } + + private suspend inline fun getOrCreate( + map: MutableMap, + filename: String, + block: () -> PreferenceProvider + ): PreferenceProvider = semaphore.withLock { map.getOrPut(filename, block) } }