| FazBrowse GitHub Viewer | Trending | | Home |
| Tools: [Download Repo ZIP] [Original HTTPS Page] |
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| Expand Up | @@ -5,53 +5,52 @@ | |
| import io.sentry.ISentryLifecycleToken; | ||
| import io.sentry.SentryLevel; | ||
| import io.sentry.Session; | ||
| import io.sentry.transport.CurrentDateProvider; | ||
| import io.sentry.transport.ICurrentDateProvider; | ||
| import io.sentry.time.Deadline; | ||
| import io.sentry.time.EpochClock; | ||
| import io.sentry.time.MonotonicTicker; | ||
| import io.sentry.util.AutoClosableReentrantLock; | ||
| import java.util.Date; | ||
| import java.util.concurrent.Future; | ||
| import java.util.concurrent.atomic.AtomicLong; | ||
| import java.util.concurrent.TimeUnit; | ||
| import java.util.concurrent.atomic.AtomicBoolean; | ||
| import org.jetbrains.annotations.NotNull; | ||
| import org.jetbrains.annotations.Nullable; | ||
| import org.jetbrains.annotations.TestOnly; | ||
|
|
||
| final class LifecycleWatcher implements AppState.AppStateListener { | ||
|
|
||
| private final AtomicLong lastUpdatedSession = new AtomicLong(0L); | ||
|
|
||
| private final long sessionIntervalMillis; | ||
|
|
||
| /** | ||
| * When the session the app left behind stops being resumable, or null while in the foreground. | ||
| * | ||
| * <p>Only read or written while holding {@link #endSessionLock}, which is also what lets | ||
| * cancelling the pending task and taking this deadline happen as one step. | ||
| */ | ||
| private @Nullable Deadline sessionEnd; | ||
|
|
||
| private @Nullable Future<?> endSessionFuture; | ||
| private final @NotNull AutoClosableReentrantLock endSessionLock = new AutoClosableReentrantLock(); | ||
| private final @NotNull IScopes scopes; | ||
| private final boolean enableSessionTracking; | ||
| private final boolean enableAppLifecycleBreadcrumbs; | ||
|
|
||
| private final @NotNull ICurrentDateProvider currentDateProvider; | ||
|
|
||
| LifecycleWatcher( | ||
| final @NotNull IScopes scopes, | ||
| final long sessionIntervalMillis, | ||
| final boolean enableSessionTracking, | ||
| final boolean enableAppLifecycleBreadcrumbs) { | ||
| this( | ||
| scopes, | ||
| sessionIntervalMillis, | ||
| enableSessionTracking, | ||
| enableAppLifecycleBreadcrumbs, | ||
| CurrentDateProvider.getInstance()); | ||
| } | ||
| private final @NotNull MonotonicTicker ticker; | ||
| private final @NotNull EpochClock epochClock; | ||
|
|
||
| LifecycleWatcher( | ||
| final @NotNull IScopes scopes, | ||
| final long sessionIntervalMillis, | ||
| final boolean enableSessionTracking, | ||
| final boolean enableAppLifecycleBreadcrumbs, | ||
| final @NotNull ICurrentDateProvider currentDateProvider) { | ||
| final @NotNull MonotonicTicker ticker, | ||
| final @NotNull EpochClock epochClock) { | ||
| this.sessionIntervalMillis = sessionIntervalMillis; | ||
| this.enableSessionTracking = enableSessionTracking; | ||
| this.enableAppLifecycleBreadcrumbs = enableAppLifecycleBreadcrumbs; | ||
| this.scopes = scopes; | ||
| this.currentDateProvider = currentDateProvider; | ||
| this.ticker = ticker; | ||
| this.epochClock = epochClock; | ||
| } | ||
|
|
||
| @Override | ||
| Expand All | @@ -61,40 +60,48 @@ public void onForeground() { | |
| } | ||
|
|
||
| private void startSession() { | ||
| cancelTask(); | ||
|
|
||
| final long currentTimeMillis = currentDateProvider.getCurrentTimeMillis(); | ||
| final @Nullable Deadline sessionEnd = takeSessionEnd(); | ||
|
|
||
| scopes.configureScope( | ||
| scope -> { | ||
| if (lastUpdatedSession.get() == 0L) { | ||
| final @Nullable Session currentSession = scope.getSession(); | ||
| if (currentSession != null && currentSession.getStarted() != null) { | ||
| lastUpdatedSession.set(currentSession.getStarted().getTime()); | ||
| } | ||
| } | ||
| }); | ||
|
|
||
| final long lastUpdatedSession = this.lastUpdatedSession.get(); | ||
| final boolean startNewSession = | ||
| lastUpdatedSession == 0L | ||
| || (lastUpdatedSession + sessionIntervalMillis) <= currentTimeMillis; | ||
| sessionEnd != null ? sessionEnd.hasPassed() : isSessionOnScopeStale(); | ||
| if (startNewSession) { | ||
| if (enableSessionTracking) { | ||
| scopes.startSession(); | ||
| } | ||
| } | ||
| scopes.getOptions().getReplayController().onAppForegrounded(startNewSession); | ||
| this.lastUpdatedSession.set(currentTimeMillis); | ||
| } | ||
|
|
||
| /** | ||
| * Whether the session on the scope is too old to resume, so foregrounding should start a new one. | ||
| * | ||
| * <p>Used when no background window is pending, which means the session was started by SDK init | ||
| * rather than by leaving and returning to the app. Nothing captured a tick back then, and the | ||
| * only record of when the session started is {@link Session#getStarted()} — a wall-clock instant, | ||
| * because it is sent to Sentry. So this check stays on the wall clock, clock steps included. | ||
| * | ||
| * <p>TODO [MAJOR]: let a session remember the tick it started on, so this can use a {@link | ||
| * Deadline} too. That tick must not be serialized. | ||
| */ | ||
| private boolean isSessionOnScopeStale() { | ||
| final long nowMillis = TimeUnit.NANOSECONDS.toMillis(epochClock.now().epochNanos()); | ||
| // No session, or one that never recorded a start, leaves nothing to resume. | ||
| final @NotNull AtomicBoolean stale = new AtomicBoolean(true); | ||
| scopes.configureScope( | ||
| scope -> { | ||
| final @Nullable Session session = scope.getSession(); | ||
| final @Nullable Date started = session == null ? null : session.getStarted(); | ||
| if (started != null) { | ||
| stale.set(started.getTime() + sessionIntervalMillis <= nowMillis); | ||
| } | ||
| }); | ||
| return stale.get(); | ||
| } | ||
|
|
||
| // App went to background and triggered this callback after 700ms | ||
| // as no new screen was shown | ||
| @Override | ||
| public void onBackground() { | ||
| final long currentTimeMillis = currentDateProvider.getCurrentTimeMillis(); | ||
|
Comment thread
Copy link
Copy Markdown
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Qualitydoesn't this change the behavior of the code? All good, I see this is now handled via sessionEnd.
Sorry, something went wrong.
runningcode reacted with thumbs up emoji
All reactions
|
||
| this.lastUpdatedSession.set(currentTimeMillis); | ||
|
|
||
| scopes.getOptions().getReplayController().onAppBackgrounded(); | ||
| scheduleEndSession(); | ||
|
|
||
| Expand All | @@ -104,6 +111,9 @@ public void onBackground() { | |
| private void scheduleEndSession() { | ||
| try (final @NotNull ISentryLifecycleToken ignored = endSessionLock.acquire()) { | ||
| cancelTask(); | ||
| final @NotNull Deadline sessionEnd = | ||
| Deadline.after(ticker, sessionIntervalMillis, TimeUnit.MILLISECONDS); | ||
| this.sessionEnd = sessionEnd; | ||
| final @NotNull Runnable endSession = | ||
| () -> { | ||
| if (enableSessionTracking) { | ||
| Expand All | @@ -114,11 +124,13 @@ private void scheduleEndSession() { | |
| }; | ||
|
|
||
| try { | ||
| // The executor's own delay stops while the device is suspended, while the deadline keeps | ||
| // counting, so this task can only run at or after the deadline. It needs no second check. | ||
| endSessionFuture = | ||
| scopes | ||
| .getOptions() | ||
| .getTimerExecutorService() | ||
| .schedule(endSession, sessionIntervalMillis); | ||
| .schedule(endSession, sessionEnd.remaining(TimeUnit.MILLISECONDS)); | ||
| } catch (Throwable e) { | ||
| scopes | ||
| .getOptions() | ||
| Expand All | @@ -131,6 +143,16 @@ private void scheduleEndSession() { | |
| } | ||
| } | ||
|
|
||
| /** Stops the pending end of session and hands back the deadline it was going to run at. */ | ||
| private @Nullable Deadline takeSessionEnd() { | ||
| try (final @NotNull ISentryLifecycleToken ignored = endSessionLock.acquire()) { | ||
| cancelTask(); | ||
| final @Nullable Deadline sessionEnd = this.sessionEnd; | ||
| this.sessionEnd = null; | ||
| return sessionEnd; | ||
| } | ||
| } | ||
|
|
||
| private void cancelTask() { | ||
| try (final @NotNull ISentryLifecycleToken ignored = endSessionLock.acquire()) { | ||
| if (endSessionFuture != null) { | ||
| Expand Down | ||
| Back | FazBrowse Home | New Git URL |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Choose a reason Spam Abuse Off Topic Outdated Duplicate Resolved Low Qualitynice refactor, this will keep future timing changes more isolated 👍
Sorry, something went wrong.
Uh oh!
There was an error while loading. Please reload this page.