Skip to content

Commit 116c481

Browse files
committed
Implement ProcessDataManager (#6961)
Implement ProcessDataManager in the way we designed in https://docs.google.com/document/d/1yEd8dCIzwNwLhRRwkDtzWFx9vxV5d-eQU-P47wDL40k/edit?usp=sharing Tested manually in the sessions test app, and with unit tests
1 parent a42bae2 commit 116c481

File tree

3 files changed

+96
-16
lines changed

3 files changed

+96
-16
lines changed

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

Lines changed: 43 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -23,32 +23,48 @@ import javax.inject.Singleton
2323

2424
/** Manage process data, used for detecting cold app starts. */
2525
internal interface ProcessDataManager {
26-
/** An in-memory uuid to uniquely identify this instance of this process. */
26+
/** This process's name. */
27+
val myProcessName: String
28+
29+
/** This process's pid. */
30+
val myPid: Int
31+
32+
/** An in-memory uuid to uniquely identify this instance of this process, not the uid. */
2733
val myUuid: String
2834

2935
/** Checks if this is a cold app start, meaning all processes in the mapping table are stale. */
3036
fun isColdStart(processDataMap: Map<String, ProcessData>): Boolean
3137

38+
/** Checks if this process is stale. */
39+
fun isMyProcessStale(processDataMap: Map<String, ProcessData>): Boolean
40+
3241
/** Call to notify the process data manager that a session has been generated. */
3342
fun onSessionGenerated()
3443

3544
/** Update the mapping of the current processes with data about this process. */
3645
fun updateProcessDataMap(processDataMap: Map<String, ProcessData>?): Map<String, ProcessData>
3746

38-
/** Generate a new mapping of process data with the current process only. */
39-
fun generateProcessDataMap() = updateProcessDataMap(mapOf())
47+
/** Generate a new mapping of process data about this process only. */
48+
fun generateProcessDataMap(): Map<String, ProcessData> = updateProcessDataMap(emptyMap())
4049
}
4150

42-
/** Manage process data, used for detecting cold app starts. */
4351
@Singleton
4452
internal class ProcessDataManagerImpl
4553
@Inject
46-
constructor(private val appContext: Context, private val uuidGenerator: UuidGenerator) :
47-
ProcessDataManager {
54+
constructor(private val appContext: Context, uuidGenerator: UuidGenerator) : ProcessDataManager {
55+
/**
56+
* This process's name.
57+
*
58+
* This value is cached, so will not reflect changes to the process name during runtime.
59+
*/
60+
override val myProcessName: String by lazy { myProcessDetails.processName }
61+
62+
override val myPid = Process.myPid()
63+
4864
override val myUuid: String by lazy { uuidGenerator.next().toString() }
4965

50-
private val myProcessName: String by lazy {
51-
ProcessDetailsProvider.getCurrentProcessDetails(appContext).processName
66+
private val myProcessDetails by lazy {
67+
ProcessDetailsProvider.getCurrentProcessDetails(appContext)
5268
}
5369

5470
private var hasGeneratedSession: Boolean = false
@@ -59,7 +75,8 @@ constructor(private val appContext: Context, private val uuidGenerator: UuidGene
5975
return false
6076
}
6177

62-
return ProcessDetailsProvider.getAppProcessDetails(appContext)
78+
// A cold start is when all app processes are stale
79+
return getAppProcessDetails()
6380
.mapNotNull { processDetails ->
6481
processDataMap[processDetails.processName]?.let { processData ->
6582
Pair(processDetails, processData)
@@ -68,6 +85,11 @@ constructor(private val appContext: Context, private val uuidGenerator: UuidGene
6885
.all { (processDetails, processData) -> isProcessStale(processDetails, processData) }
6986
}
7087

88+
override fun isMyProcessStale(processDataMap: Map<String, ProcessData>): Boolean {
89+
val myProcessData = processDataMap[myProcessName] ?: return true
90+
return myProcessData.pid != myPid || myProcessData.uuid != myUuid
91+
}
92+
7193
override fun onSessionGenerated() {
7294
hasGeneratedSession = true
7395
}
@@ -81,17 +103,22 @@ constructor(private val appContext: Context, private val uuidGenerator: UuidGene
81103
?.toMap()
82104
?: mapOf(myProcessName to ProcessData(Process.myPid(), myUuid))
83105

106+
/** Gets the current details for all of the app's running processes. */
107+
private fun getAppProcessDetails() = ProcessDetailsProvider.getAppProcessDetails(appContext)
108+
84109
/**
85110
* Returns true if the process is stale, meaning the persisted process data does not match the
86111
* running process details.
112+
*
113+
* The [processDetails] is the running process details, and [processData] is the persisted data.
87114
*/
88-
private fun isProcessStale(
89-
runningProcessDetails: ProcessDetails,
90-
persistedProcessData: ProcessData,
91-
): Boolean =
92-
if (myProcessName == runningProcessDetails.processName) {
93-
runningProcessDetails.pid != persistedProcessData.pid || myUuid != persistedProcessData.uuid
115+
private fun isProcessStale(processDetails: ProcessDetails, processData: ProcessData): Boolean =
116+
if (myProcessName == processDetails.processName) {
117+
// For this process, check pid and uuid
118+
processDetails.pid != processData.pid || myUuid != processData.uuid
94119
} else {
95-
runningProcessDetails.pid != persistedProcessData.pid
120+
// For other processes, only check pid to avoid inter-process communication
121+
// It is very unlikely for there to be a pid collision
122+
processDetails.pid != processData.pid
96123
}
97124
}

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

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -100,6 +100,53 @@ internal class ProcessDataManagerTest {
100100
assertThat(coldStart).isFalse()
101101
}
102102

103+
@Test
104+
fun isMyProcessStale() {
105+
val appContext =
106+
FakeFirebaseApp(processes = listOf(myProcessInfo)).firebaseApp.applicationContext
107+
val processDataManager = ProcessDataManagerImpl(appContext, FakeUuidGenerator(UUID_1))
108+
109+
val myProcessStale =
110+
processDataManager.isMyProcessStale(mapOf(MY_PROCESS_NAME to ProcessData(MY_PID, UUID_1)))
111+
112+
assertThat(myProcessStale).isFalse()
113+
}
114+
115+
@Test
116+
fun isMyProcessStale_otherProcessCurrent() {
117+
val appContext =
118+
FakeFirebaseApp(processes = listOf(myProcessInfo, otherProcessInfo))
119+
.firebaseApp
120+
.applicationContext
121+
val processDataManager = ProcessDataManagerImpl(appContext, FakeUuidGenerator(UUID_1))
122+
123+
val myProcessStale =
124+
processDataManager.isMyProcessStale(
125+
mapOf(
126+
MY_PROCESS_NAME to ProcessData(OTHER_PID, UUID_1),
127+
OTHER_PROCESS_NAME to ProcessData(OTHER_PID, UUID_2),
128+
)
129+
)
130+
131+
assertThat(myProcessStale).isTrue()
132+
}
133+
134+
@Test
135+
fun isMyProcessStale_missingMyProcessData() {
136+
val appContext =
137+
FakeFirebaseApp(processes = listOf(myProcessInfo, otherProcessInfo))
138+
.firebaseApp
139+
.applicationContext
140+
val processDataManager = ProcessDataManagerImpl(appContext, FakeUuidGenerator(UUID_1))
141+
142+
val myProcessStale =
143+
processDataManager.isMyProcessStale(
144+
mapOf(OTHER_PROCESS_NAME to ProcessData(OTHER_PID, UUID_2))
145+
)
146+
147+
assertThat(myProcessStale).isTrue()
148+
}
149+
103150
@After
104151
fun cleanUp() {
105152
FirebaseApp.clearInstancesForTest()

firebase-sessions/src/test/kotlin/com/google/firebase/sessions/testing/FakeProcessDataManager.kt

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,9 @@ import com.google.firebase.sessions.ProcessDataManager
2525
*/
2626
internal class FakeProcessDataManager(
2727
private val coldStart: Boolean = false,
28+
private var myProcessStale: Boolean = false,
29+
override val myProcessName: String = "com.google.firebase.sessions.test",
30+
override var myPid: Int = 0,
2831
override var myUuid: String = FakeUuidGenerator.UUID_1,
2932
) : ProcessDataManager {
3033
private var hasGeneratedSession: Boolean = false
@@ -33,9 +36,12 @@ internal class FakeProcessDataManager(
3336
if (hasGeneratedSession) {
3437
return false
3538
}
39+
3640
return coldStart
3741
}
3842

43+
override fun isMyProcessStale(processDataMap: Map<String, ProcessData>): Boolean = myProcessStale
44+
3945
override fun onSessionGenerated() {
4046
hasGeneratedSession = true
4147
}

0 commit comments

Comments
 (0)