Skip to content

Commit 7981a81

Browse files
themiswangmrober
authored andcommitted
Fallback try catch block (#6873)
Using try catch block to make datastore fall back to a pre multi-process supported version. Generate a new session locally then notify to subscribers
1 parent 1d183fe commit 7981a81

File tree

1 file changed

+73
-21
lines changed

1 file changed

+73
-21
lines changed

firebase-sessions/src/main/kotlin/com/google/firebase/sessions/SharedSessionRepository.kt

Lines changed: 73 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,9 @@ import javax.inject.Inject
2727
import javax.inject.Singleton
2828
import kotlin.coroutines.CoroutineContext
2929
import kotlinx.coroutines.CoroutineScope
30+
import kotlinx.coroutines.flow.catch
3031
import kotlinx.coroutines.launch
32+
import org.jetbrains.annotations.VisibleForTesting
3133

3234
/** Repository to persist session data to be shared between all app processes. */
3335
internal interface SharedSessionRepository {
@@ -48,20 +50,38 @@ constructor(
4850
@Background private val backgroundDispatcher: CoroutineContext,
4951
) : SharedSessionRepository {
5052
/** Local copy of the session data. Can get out of sync, must be double-checked in datastore. */
51-
private lateinit var localSessionData: SessionData
53+
@VisibleForTesting lateinit var localSessionData: SessionData
54+
55+
/**
56+
* Either notify the subscribers with general multi-process supported session or fallback local
57+
* session
58+
*/
59+
private enum class NotificationType {
60+
GENERAL,
61+
FALLBACK
62+
}
5263

5364
init {
65+
println("session repo init")
5466
CoroutineScope(backgroundDispatcher).launch {
55-
sessionDataStore.data.collect { sessionData ->
56-
localSessionData = sessionData
57-
val sessionId = sessionData.sessionDetails.sessionId
58-
59-
FirebaseSessionsDependencies.getRegisteredSubscribers().values.forEach { subscriber ->
60-
// Notify subscribers, regardless of sampling and data collection state
61-
subscriber.onSessionChanged(SessionSubscriber.SessionDetails(sessionId))
62-
Log.d(TAG, "Notified ${subscriber.sessionSubscriberName} of new session $sessionId")
67+
sessionDataStore.data
68+
.catch {
69+
val newSession =
70+
SessionData(
71+
sessionDetails = sessionGenerator.generateNewSession(null),
72+
backgroundTime = timeProvider.currentTime()
73+
)
74+
Log.d(
75+
TAG,
76+
"Init session datastore failed with exception message: ${it.message}. Emit fallback session ${newSession.sessionDetails.sessionId}"
77+
)
78+
emit(newSession)
79+
}
80+
.collect { sessionData ->
81+
localSessionData = sessionData
82+
val sessionId = sessionData.sessionDetails.sessionId
83+
notifySubscribers(sessionId, NotificationType.GENERAL)
6384
}
64-
}
6585
}
6686
}
6787

@@ -74,9 +94,14 @@ constructor(
7494
Log.d(TAG, "App backgrounded on ${getProcessName()} - $sessionData")
7595

7696
CoroutineScope(backgroundDispatcher).launch {
77-
sessionDataStore.updateData {
78-
// TODO(mrober): Double check time makes sense?
79-
sessionData.copy(backgroundTime = timeProvider.currentTime())
97+
try {
98+
sessionDataStore.updateData {
99+
// TODO(mrober): Double check time makes sense?
100+
sessionData.copy(backgroundTime = timeProvider.currentTime())
101+
}
102+
} catch (ex: Exception) {
103+
Log.d(TAG, "App backgrounded, failed to update data. Message: ${ex.message}")
104+
localSessionData = localSessionData.copy(backgroundTime = timeProvider.currentTime())
80105
}
81106
}
82107
}
@@ -91,20 +116,47 @@ constructor(
91116

92117
if (shouldInitiateNewSession(sessionData)) {
93118
CoroutineScope(backgroundDispatcher).launch {
94-
sessionDataStore.updateData { currentSessionData ->
95-
// Double-check pattern
96-
if (shouldInitiateNewSession(currentSessionData)) {
97-
val newSessionDetails = sessionGenerator.generateNewSession(sessionData.sessionDetails)
98-
sessionFirelogPublisher.mayLogSession(sessionDetails = newSessionDetails)
99-
currentSessionData.copy(sessionDetails = newSessionDetails)
100-
} else {
101-
currentSessionData
119+
try {
120+
sessionDataStore.updateData { currentSessionData ->
121+
// Double-check pattern
122+
if (shouldInitiateNewSession(currentSessionData)) {
123+
val newSessionDetails =
124+
sessionGenerator.generateNewSession(sessionData.sessionDetails)
125+
sessionFirelogPublisher.mayLogSession(sessionDetails = newSessionDetails)
126+
currentSessionData.copy(sessionDetails = newSessionDetails)
127+
} else {
128+
currentSessionData
129+
}
102130
}
131+
} catch (ex: Exception) {
132+
Log.d(TAG, "App appForegrounded, failed to update data. Message: ${ex.message}")
133+
val newSessionDetails = sessionGenerator.generateNewSession(sessionData.sessionDetails)
134+
localSessionData = localSessionData.copy(sessionDetails = newSessionDetails)
135+
sessionFirelogPublisher.mayLogSession(sessionDetails = newSessionDetails)
136+
137+
val sessionId = newSessionDetails.sessionId
138+
notifySubscribers(sessionId, NotificationType.FALLBACK)
103139
}
104140
}
105141
}
106142
}
107143

144+
private suspend fun notifySubscribers(sessionId: String, type: NotificationType) {
145+
FirebaseSessionsDependencies.getRegisteredSubscribers().values.forEach { subscriber ->
146+
// Notify subscribers, regardless of sampling and data collection state
147+
subscriber.onSessionChanged(SessionSubscriber.SessionDetails(sessionId))
148+
when (type) {
149+
NotificationType.GENERAL ->
150+
Log.d(TAG, "Notified ${subscriber.sessionSubscriberName} of new session $sessionId")
151+
NotificationType.FALLBACK ->
152+
Log.d(
153+
TAG,
154+
"Notified ${subscriber.sessionSubscriberName} of new fallback session $sessionId"
155+
)
156+
}
157+
}
158+
}
159+
108160
private fun shouldInitiateNewSession(sessionData: SessionData): Boolean {
109161
val interval = timeProvider.currentTime() - sessionData.backgroundTime
110162
return interval > sessionsSettings.sessionRestartTimeout

0 commit comments

Comments
 (0)