Skip to content

Commit ebea05b

Browse files
committed
Share settings cache between running processes
1 parent 62508f2 commit ebea05b

File tree

7 files changed

+148
-89
lines changed

7 files changed

+148
-89
lines changed

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/RemoteSettings.kt

Lines changed: 3 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -23,13 +23,11 @@ import com.google.firebase.installations.FirebaseInstallationsApi
2323
import com.google.firebase.sessions.ApplicationInfo
2424
import com.google.firebase.sessions.InstallationId
2525
import com.google.firebase.sessions.TimeProvider
26-
import dagger.Lazy
2726
import javax.inject.Inject
2827
import javax.inject.Singleton
2928
import kotlin.time.Duration
3029
import kotlin.time.Duration.Companion.hours
3130
import kotlin.time.Duration.Companion.seconds
32-
import kotlinx.coroutines.runBlocking
3331
import kotlinx.coroutines.sync.Mutex
3432
import kotlinx.coroutines.sync.withLock
3533
import org.json.JSONException
@@ -43,11 +41,8 @@ constructor(
4341
private val firebaseInstallationsApi: FirebaseInstallationsApi,
4442
private val appInfo: ApplicationInfo,
4543
private val configsFetcher: CrashlyticsSettingsFetcher,
46-
private val lazySettingsCache: Lazy<SettingsCache>,
44+
private val settingsCache: SettingsCache,
4745
) : SettingsProvider {
48-
private val settingsCache: SettingsCache
49-
get() = lazySettingsCache.get()
50-
5146
private val fetchInProgress = Mutex()
5247

5348
override val sessionEnabled: Boolean?
@@ -133,7 +128,7 @@ constructor(
133128
sessionTimeoutSeconds = sessionTimeoutSeconds,
134129
sessionSamplingRate = sessionSamplingRate,
135130
cacheDurationSeconds = cacheDuration ?: defaultCacheDuration,
136-
cacheUpdatedTimeMs = timeProvider.currentTime().ms,
131+
cacheUpdatedTimeSeconds = timeProvider.currentTime().seconds,
137132
)
138133
)
139134
},
@@ -148,7 +143,7 @@ constructor(
148143
override fun isSettingsStale(): Boolean = settingsCache.hasCacheExpired()
149144

150145
@VisibleForTesting
151-
internal fun clearCachedSettings() = runBlocking {
146+
internal suspend fun clearCachedSettings() {
152147
settingsCache.updateConfigs(SessionConfigsSerializer.defaultValue)
153148
}
154149

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SessionConfigs.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ internal data class SessionConfigs(
3030
val sessionSamplingRate: Double?,
3131
val sessionTimeoutSeconds: Int?,
3232
val cacheDurationSeconds: Int?,
33-
val cacheUpdatedTimeMs: Long?,
33+
val cacheUpdatedTimeSeconds: Long?,
3434
)
3535

3636
/** DataStore json [Serializer] for [SessionConfigs]. */
@@ -41,7 +41,7 @@ internal object SessionConfigsSerializer : Serializer<SessionConfigs> {
4141
sessionSamplingRate = null,
4242
sessionTimeoutSeconds = null,
4343
cacheDurationSeconds = null,
44-
cacheUpdatedTimeMs = null,
44+
cacheUpdatedTimeSeconds = null,
4545
)
4646

4747
override suspend fun readFrom(input: InputStream): SessionConfigs =

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/settings/SettingsCache.kt

Lines changed: 29 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -17,12 +17,18 @@
1717
package com.google.firebase.sessions.settings
1818

1919
import android.util.Log
20+
import androidx.annotation.VisibleForTesting
2021
import androidx.datastore.core.DataStore
22+
import com.google.firebase.annotations.concurrent.Background
2123
import com.google.firebase.sessions.TimeProvider
2224
import java.io.IOException
25+
import java.util.concurrent.atomic.AtomicReference
2326
import javax.inject.Inject
2427
import javax.inject.Singleton
28+
import kotlin.coroutines.CoroutineContext
29+
import kotlinx.coroutines.CoroutineScope
2530
import kotlinx.coroutines.flow.first
31+
import kotlinx.coroutines.launch
2632
import kotlinx.coroutines.runBlocking
2733

2834
internal interface SettingsCache {
@@ -41,23 +47,38 @@ internal interface SettingsCache {
4147
internal class SettingsCacheImpl
4248
@Inject
4349
constructor(
50+
@Background private val backgroundDispatcher: CoroutineContext,
4451
private val timeProvider: TimeProvider,
4552
private val sessionConfigsDataStore: DataStore<SessionConfigs>,
4653
) : SettingsCache {
47-
private var sessionConfigs: SessionConfigs
54+
private val sessionConfigsAtomicReference = AtomicReference<SessionConfigs>()
55+
56+
private val sessionConfigs: SessionConfigs
57+
get() {
58+
// Ensure configs are loaded from disk before the first access
59+
if (sessionConfigsAtomicReference.get() == null) {
60+
// Double check to avoid the `runBlocking` unless necessary
61+
sessionConfigsAtomicReference.compareAndSet(
62+
null,
63+
runBlocking { sessionConfigsDataStore.data.first() },
64+
)
65+
}
66+
67+
return sessionConfigsAtomicReference.get()
68+
}
4869

4970
init {
50-
// Block until the cache is loaded from disk to ensure cache
51-
// values are valid and readable from the main thread on init.
52-
runBlocking { sessionConfigs = sessionConfigsDataStore.data.first() }
71+
CoroutineScope(backgroundDispatcher).launch {
72+
sessionConfigsDataStore.data.collect(sessionConfigsAtomicReference::set)
73+
}
5374
}
5475

5576
override fun hasCacheExpired(): Boolean {
56-
val cacheUpdatedTimeMs = sessionConfigs.cacheUpdatedTimeMs
77+
val cacheUpdatedTimeSeconds = sessionConfigs.cacheUpdatedTimeSeconds
5778
val cacheDurationSeconds = sessionConfigs.cacheDurationSeconds
5879

59-
if (cacheUpdatedTimeMs != null && cacheDurationSeconds != null) {
60-
val timeDifferenceSeconds = (timeProvider.currentTime().ms - cacheUpdatedTimeMs) / 1000
80+
if (cacheUpdatedTimeSeconds != null && cacheDurationSeconds != null) {
81+
val timeDifferenceSeconds = timeProvider.currentTime().seconds - cacheUpdatedTimeSeconds
6182
if (timeDifferenceSeconds < cacheDurationSeconds) {
6283
return false
6384
}
@@ -74,12 +95,12 @@ constructor(
7495
override suspend fun updateConfigs(sessionConfigs: SessionConfigs) {
7596
try {
7697
sessionConfigsDataStore.updateData { sessionConfigs }
77-
this.sessionConfigs = sessionConfigs
7898
} catch (ex: IOException) {
7999
Log.w(TAG, "Failed to update config values: $ex")
80100
}
81101
}
82102

103+
@VisibleForTesting
83104
internal suspend fun removeConfigs() =
84105
try {
85106
sessionConfigsDataStore.updateData { SessionConfigsSerializer.defaultValue }

firebase-sessions/src/test/kotlin/com/google/firebase/sessions/SessionDatastoreTest.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -44,7 +44,7 @@ class SessionDatastoreTest {
4444
DataStoreFactory.create(
4545
serializer = SessionDataSerializer,
4646
scope = CoroutineScope(StandardTestDispatcher(testScheduler, "blocking")),
47-
produceFile = { appContext.dataStoreFile("sessionDataTestDataStore.data") },
47+
produceFile = { appContext.dataStoreFile("sessionDataStore.data") },
4848
),
4949
)
5050

0 commit comments

Comments
 (0)