Repository navigation
feat: [SDK-5390] send a session end when a sessions API session ends - #2781
fadi-george wants to merge 3 commits into
Conversation
📊 Diff Coverage ReportDiff Coverage Report (Changed Lines Only)Gate: aggregate coverage on changed executable lines must be ≥ 80% (JaCoCo line data for lines touched in the diff). Changed Files Coverage
Overall (aggregate gate)35/35 touched executable lines covered (100.0% — requires ≥ 80%) |
abdulraqeeb33
left a comment
There was a problem hiding this comment.
Requesting changes.
The end uses the same id check as the create in #2780. If any id is missing, toEndOperation returns null, then the session is invalidated and activeDuration is zeroed. The end is gone, and a later retry has nothing left to send.
Same fix as #2780: enqueue and let the executor wait, and only clear the session once the end is queued.
454a642 to
24ec2e1
Compare
abdulraqeeb33
left a comment
There was a problem hiding this comment.
The missing-id finding is closed. initUser runs before startup, create and end share the id check, and the queue drops an end whose create was never queued.
One blocker: backgroundRun is the first suspend in this method, and BackgroundManager runs the background services on Dispatchers.Unconfined. When enqueueAndWait resumes, the rest of that loop, including scheduleBackground and its JobScheduler calls, continues on the thread that woke the waiter. On success that is the operation-repo thread, so those binder calls stall the queue. Wrap the wait in withContext(Dispatchers.IO).
Low: a new session does not clear unfocusTime. endTime is maxOf(unfocusTime, focusTime), so a backward clock step plus a kill during the next focus reports the previous session's unfocus. Set unfocusTime to null with the other new-session fields.
When the session uses the sessions API, enqueue an end UpdateSessionOperation with the backgrounded-at time and final duration. In the background job, flush and wait for it with a timeout so Android 15+ doesn't block the request after the job finishes. Flag off leaves the legacy end unchanged. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
24ec2e1 to
6ffdc9f
Compare
… on a new session BackgroundManager runs background services on Dispatchers.Unconfined, so the rest of its loop resumed on the operation repo thread after the end was sent. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
|
Thanks, both fixed in e06a1da.
|
abdulraqeeb33
left a comment
There was a problem hiding this comment.
The IO wrap fixes both resumes. A successful wake and the 30s timeout both continue BackgroundManager on an IO thread. unfocusTime is cleared on the new session.
The timeout test is a race, and it is why CI is red.
runTest advanced past the 30s timeout while the send was parked on the real IO dispatcher, so the wait sometimes never started. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
abdulraqeeb33
left a comment
There was a problem hiding this comment.
The timeout test stays on the virtual clock. currentTime hits 30s because the wait was entered and then cancelled, and CI is green. Production still waits on Dispatchers.IO.
Description
One Line Summary
When the sessions API flag is on, send a session end
PATCHwithend_timeand the final duration when a session ends.Details
Motivation
Part of the SDK Sessions stack (SDK-5390). Ended sessions need a
PATCH /apps/{app_id}/sessions/{session_id}withend_timeand the final cumulativeduration_seconds. Based onnew-session-events.Scope
SessionService.endSession()(timeout expiry in the background job, or cold start ending a prior session) builds an endUpdateSessionOperationwhen the session'susesSessionsApiis true:end_timeis the backgrounded-at time, tracked in a new persistedSessionModel.unfocusTime. An app killed while focused has no unfocus after its last focus, so the last focus time is used instead.activeDuration.localSessionId. The queue swaps in the server ID once the create succeeds.SessionQueuePolicyand its persisted session state, which also gives the end the IDs its create sent, so no queue changes.backgroundRun()the end is sent withenqueueAndWait(flush = true)and a 30 s timeout, so the request starts while theSyncJobServicejob is still running instead of after the 5 s batching delay. Flushing is allowed here because it happens at most once per session, and session start already flushes. On timeout the end stays queued and replays on the next app open.TrackSessionEndOperationis unchanged in both cases; turning off legacy reporting is SDK-5388.SessionServiceunder detekt limits.Testing
Unit testing
Added
SessionServiceTestscases: end fields and flushed wait in the background run, returning on timeout, cold start ending the previous session before the new create, end time after a kill while focused, no end when the session uses the legacy path, and no end without a push subscription ID.unfocusTimeadded to the persistence test.spotlessCheck,detekt,apiCheck,testReleaseUnitTest(core), andassembleReleasepass locally.Manual testing
Not tested on device yet, including the Android 15 background send; end-to-end verification is planned in the Android Rollout & QA ticket (SDK-5405).
Affected code checklist
Checklist
Overview
Testing
Final pass
🤖 Generated with Claude Code