fix(android): Decide session rotation on a monotonic clock (JAVA-573) - #6096
Open
runningcode wants to merge 4 commits into
Open
fix(android): Decide session rotation on a monotonic clock (JAVA-573)#6096runningcode wants to merge 4 commits into
runningcode wants to merge 4 commits into
Conversation
Whether foregrounding rotates the session was a comparison between two System.currentTimeMillis() readings taken up to 30 seconds apart. The gap between two wall-clock reads is not the time that passed — the device syncs its clock and the interval comes out too long, too short, or negative — so a step forward started a session that should have been resumed, and a step back resumed one that should have ended. Android steps the wall clock most often in the first seconds after boot, which is exactly where a cold start's background window lives. The background window is a duration, so it now lives on a Deadline over options.getMonotonicTicker(), which on Android is CLOCK_BOOTTIME and so keeps counting through deep sleep. One deadline serves both halves of the window: the end-session task is scheduled for its remaining(), and a foreground arriving first asks whether it hasPassed(). Measuring one window in two places is what allowed two readings to disagree. The staleness of a session already on the scope stays on the wall clock and is now named for it. That path is only reached before the app has been backgrounded in this process, and the only record of when that session started is Session.getStarted() — a serialized epoch instant, which no tick can be compared against. A TODO [MAJOR] marks the real fix: a session that records the tick it started on. Passing the clocks in from AppLifecycleIntegration, which already holds the options being registered, removes the second constructor rather than adding a third parameter to it. Two tests were pinning the old behavior: `if last started session is before interval` stubbed the clock with (2, 1) and passed only because the wall clock ran backwards. They become a real background-foreground cycle on a TestMonotonicTicker, with one regression test per step direction. This leaves the other half of JAVA-573 open: a session that slept through its window is still stamped when the SDK noticed rather than when the window fell due, so its duration is inflated by the suspend. That value is serialized, and #6091 is the template for fixing it. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
📲 Install BuildsAndroid
|
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
…(JAVA-573) Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
runningcode
marked this pull request as ready for review
September 11, 2026 13:36
runningcode
requested review from
0xadam-brown,
adinauer,
markushi and
romtsn
as code owners
September 11, 2026 13:36
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
📜 Description
Background context:
The SDK decides it is a new session if you leave the app for more than 30 seconds. So if you come back under 30 seconds you use the same session. More than 30 seconds you get a new session.
The problem is, yup, you guessed it, we're using a wall clock time to determine these 30 seconds. The wall clock could jump backwards or forwards within these 30 seconds and then we make an incorrect decision on whether or not create a new session.
This swaps the logic to use a monotonic clock and a
Deadline.The code is also simpler now. Before there were two places that would check to end the session. One was to check
lastUpdatedSessionagainst thecurrentTimeMilliswhich is the WallClock. The other was the.schedule(endSession, sessionIntervalMillis)which is a monotonic clock. They could both have different values.Not fixed here, deliberately: the other half of JAVA-573. A session that slept through its background window is still stamped when the SDK noticed rather than when the window is due, so its duration is inflated by the suspend. That value is serialized which is why we aren't fixing it here. #6091 is a template for fixing it. Its
Expiryis the shape to reuse, and probably wants lifting out ofSentryTracerfirst.💡 Motivation and Context
Part of the clock semantics hardening tracked in JAVA-557 §B2. Note that §B2's premise is stale on one point:
java.util.Timerwas already replaced by the shared timer executor in #5819, so this PR is about which clock the window is measured on, not about the timer type.💚 How did you test it?
Its hard to reproduce deep sleep and clock pinning but the logic is tested with some new unit tests.
📝 Checklist
sendDefaultPIIis enabled.No
.apidiff —LifecycleWatcheris package-private.🔮 Next steps
Stamping the session end at the instant the window is due, so a suspended session stops reporting the sleep as app usage.
🤖 Generated with Claude Code