Skip to content

Commit

Permalink
Always start a new session span after a manual session end (#1167)
Browse files Browse the repository at this point in the history
## Goal

Manual session ends only started a new session span if background activity is enabled. This is because there's ambiguity in how we decided whether we should new session span.

To address this, I've consolidated the decision in the `FinalEvelopeParam` definition and removed it from the `SessionSnapshotType`, what is defined in FinalEvelopeParam is the ultimate arbitor.

## Testing

Added additional testing to verify the correctness at the PayloadFactory level as well as the PayloadMessageCollator level.
  • Loading branch information
bidetofevil authored Jul 31, 2024
1 parent e8b7fb4 commit e2f104a
Show file tree
Hide file tree
Showing 7 changed files with 49 additions and 112 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@ public class FinalEnvelopeParams(
public val initial: SessionZygote,
public val endType: SessionSnapshotType,
public val logger: EmbLogger,
backgroundActivityEnabled: Boolean,
continueMonitoring: Boolean,
crashId: String? = null,
) {

Expand All @@ -21,5 +21,5 @@ public class FinalEnvelopeParams(
else -> crashId
}

public val startNewSession: Boolean = endType.shouldStartNewSession && backgroundActivityEnabled
public val startNewSession: Boolean = continueMonitoring
}
Original file line number Diff line number Diff line change
Expand Up @@ -14,26 +14,21 @@ public enum class SessionSnapshotType(
* Whether the session process experienced a force quit/unexpected termination.
*/
public val forceQuit: Boolean,

/**
* Whether this type of end should lead to the start of a new session
*/
public val shouldStartNewSession: Boolean
) {

/**
* The end session happened in the normal way (i.e. process state changes or manual/timed end).
*/
NORMAL_END(endedCleanly = true, forceQuit = false, shouldStartNewSession = true),
NORMAL_END(endedCleanly = true, forceQuit = false),

/**
* The end session is being constructed so that it can be periodically cached. This avoids
* the scenario of data loss in the event of NDK crashes.
*/
PERIODIC_CACHE(endedCleanly = false, forceQuit = true, shouldStartNewSession = false),
PERIODIC_CACHE(endedCleanly = false, forceQuit = true),

/**
* The end session is being constructed because of a JVM crash.
*/
JVM_CRASH(endedCleanly = false, forceQuit = false, shouldStartNewSession = false)
JVM_CRASH(endedCleanly = false, forceQuit = false)
}
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ internal class PayloadFactoryImpl(
initial = initial,
endType = SessionSnapshotType.NORMAL_END,
logger = logger,
backgroundActivityEnabled = isBackgroundActivityEnabled(),
continueMonitoring = true,
)
)
}
Expand Down Expand Up @@ -104,7 +104,7 @@ internal class PayloadFactoryImpl(
initial = initial,
endType = SessionSnapshotType.NORMAL_END,
logger = logger,
backgroundActivityEnabled = isBackgroundActivityEnabled(),
continueMonitoring = isBackgroundActivityEnabled(),
)
)
}
Expand All @@ -121,7 +121,7 @@ internal class PayloadFactoryImpl(
initial = initial,
endType = SessionSnapshotType.NORMAL_END,
logger = logger,
backgroundActivityEnabled = true,
continueMonitoring = true,
)
)
}
Expand All @@ -135,7 +135,7 @@ internal class PayloadFactoryImpl(
initial = initial,
endType = SessionSnapshotType.JVM_CRASH,
logger = logger,
backgroundActivityEnabled = isBackgroundActivityEnabled(),
continueMonitoring = false,
crashId = crashId
)
)
Expand All @@ -153,7 +153,7 @@ internal class PayloadFactoryImpl(
initial = initial,
endType = SessionSnapshotType.JVM_CRASH,
logger = logger,
backgroundActivityEnabled = true,
continueMonitoring = false,
crashId = crashId
)
)
Expand All @@ -168,7 +168,7 @@ internal class PayloadFactoryImpl(
initial = initial,
endType = SessionSnapshotType.PERIODIC_CACHE,
logger = logger,
backgroundActivityEnabled = isBackgroundActivityEnabled()
continueMonitoring = true
)
)
}
Expand All @@ -182,7 +182,7 @@ internal class PayloadFactoryImpl(
initial = initial,
endType = SessionSnapshotType.PERIODIC_CACHE,
logger = logger,
backgroundActivityEnabled = true
continueMonitoring = true
)
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,14 @@ import io.embrace.android.embracesdk.internal.session.orchestrator.SessionSnapsh
internal class FakeSessionPayloadSource : SessionPayloadSource {

var sessionPayload: SessionPayload = SessionPayload()
var lastStartNewSession: Boolean? = null

override fun getSessionPayload(
endType: SessionSnapshotType,
startNewSession: Boolean,
crashId: String?
) = sessionPayload
): SessionPayload {
lastStartNewSession = startNewSession
return SessionPayload()
}
}

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,13 @@ import io.embrace.android.embracesdk.fakes.FakePreferenceService
import io.embrace.android.embracesdk.fakes.FakeSessionPayloadSource
import io.embrace.android.embracesdk.fakes.injection.FakeInitModule
import io.embrace.android.embracesdk.internal.envelope.session.SessionEnvelopeSourceImpl
import io.embrace.android.embracesdk.internal.session.lifecycle.ProcessState
import io.embrace.android.embracesdk.internal.session.lifecycle.ProcessState.BACKGROUND
import io.embrace.android.embracesdk.internal.session.lifecycle.ProcessState.FOREGROUND
import io.embrace.android.embracesdk.internal.session.message.PayloadFactoryImpl
import io.embrace.android.embracesdk.internal.session.message.PayloadMessageCollatorImpl
import org.junit.Assert.assertEquals
import org.junit.Assert.assertNotNull
import org.junit.Assert.assertNull
import org.junit.Assert.assertTrue
import org.junit.Before
Expand All @@ -20,20 +23,22 @@ import org.junit.Test
internal class PayloadFactoryImplTest {

private lateinit var configService: FakeConfigService
private lateinit var sessionPayloadSource: FakeSessionPayloadSource
private lateinit var factory: PayloadFactoryImpl

@Before
fun setUp() {
configService = FakeConfigService()
val initModule = FakeInitModule()
configService = FakeConfigService()
sessionPayloadSource = FakeSessionPayloadSource()
val collator = PayloadMessageCollatorImpl(
gatingService = FakeGatingService(),
preferencesService = FakePreferenceService(),
currentSessionSpan = initModule.openTelemetryModule.currentSessionSpan,
sessionEnvelopeSource = SessionEnvelopeSourceImpl(
FakeEnvelopeMetadataSource(),
FakeEnvelopeResourceSource(),
FakeSessionPayloadSource()
sessionPayloadSource
)
)
factory = PayloadFactoryImpl(
Expand All @@ -50,29 +55,36 @@ internal class PayloadFactoryImplTest {
}

@Test
fun `start payload with state generate payloads with valid session IDs if ba is enabled`() {
fun `verify expected payloads with ba enabled`() {
configService.backgroundActivityCaptureEnabled = true
assertTrue(checkNotNull(factory.startPayloadWithState(FOREGROUND, 0, false)).sessionId.isNotBlank())
assertTrue(checkNotNull(factory.startPayloadWithState(BACKGROUND, 0, false)).sessionId.isNotBlank())
verifyPayloadWithState(state = FOREGROUND, zygoteCreated = true, startNewSession = true)
verifyPayloadWithState(state = BACKGROUND, zygoteCreated = true, startNewSession = true)
verifyPayloadWithManual()
}

@Test
fun `start payload with state generate payloads with valid session IDs only for foreground if ba is disabled`() {
fun `verify expected payloads with ba disabled`() {
configService.backgroundActivityCaptureEnabled = false
assertTrue(checkNotNull(factory.startPayloadWithState(FOREGROUND, 0, false)).sessionId.isNotBlank())
assertNull(factory.startPayloadWithState(BACKGROUND, 0, false))
verifyPayloadWithState(state = FOREGROUND, zygoteCreated = true, startNewSession = false)
verifyPayloadWithState(state = BACKGROUND, zygoteCreated = false, startNewSession = false)
verifyPayloadWithManual()
}

@Test
fun `start payload with manual generate payloads with valid session IDs if ba is enabled`() {
configService.backgroundActivityCaptureEnabled = true
assertTrue(checkNotNull(factory.startSessionWithManual(100L)).sessionId.isNotBlank())
assertTrue(checkNotNull(factory.startPayloadWithState(BACKGROUND, 0, false)).sessionId.isNotBlank())
private fun verifyPayloadWithState(state: ProcessState, zygoteCreated: Boolean, startNewSession: Boolean) {
val zygote = factory.startPayloadWithState(state, 0, false)
if (zygoteCreated) {
assertTrue(checkNotNull(zygote).sessionId.isNotBlank())
assertNotNull(factory.endPayloadWithState(state, 0, zygote))
assertEquals(startNewSession, sessionPayloadSource.lastStartNewSession)
} else {
assertNull(zygote)
}
}

@Test
fun `start payload with manual generate payloads with valid session IDs if ba is disabled`() {
configService.backgroundActivityCaptureEnabled = false
assertTrue(checkNotNull(factory.startSessionWithManual(100L)).sessionId.isNotBlank())
private fun verifyPayloadWithManual() {
val zygote = factory.startSessionWithManual(0)
assertTrue(zygote.sessionId.isNotBlank())
assertNotNull(factory.endSessionWithManual(0, zygote))
assertEquals(true, sessionPayloadSource.lastStartNewSession)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -96,7 +96,7 @@ internal class PayloadMessageCollatorImplTest {
initial = startMsg,
endType = SessionSnapshotType.NORMAL_END,
logger = initModule.logger,
backgroundActivityEnabled = true,
continueMonitoring = true,
crashId = "crashId"
)
)
Expand All @@ -123,7 +123,7 @@ internal class PayloadMessageCollatorImplTest {
initial = startMsg,
endType = SessionSnapshotType.NORMAL_END,
logger = initModule.logger,
backgroundActivityEnabled = true,
continueMonitoring = true,
crashId = "crashId",
)
)
Expand Down

0 comments on commit e2f104a

Please sign in to comment.