Skip to content

ref(android): Confine replay lifecycle to main thread - #5965

Open
romtsn wants to merge 8 commits into
mainfrom
romtsn/feat/java-665-replay-start-stop
Open

ref(android): Confine replay lifecycle to main thread#5965
romtsn wants to merge 8 commits into
mainfrom
romtsn/feat/java-665-replay-start-stop

Conversation

@romtsn

@romtsn romtsn commented Aug 13, 2026

Copy link
Copy Markdown
Member

📜 Description

Serialize Session Replay lifecycle changes through the Android main looper and keep the active lifecycle, capture strategy, replay ID, and generation in one atomic state value. Every lifecycle command is queued, including calls already made from main, which preserves ordering across callers and defers replay startup beyond the SDK initialization critical path.

This removes the replay lifecycle and encoder locks that could block Android lifecycle callbacks and cause deadlocks or ANRs. Encoder work and cache cleanup remain ordered on the replay executor. Close performs main-thread teardown before executor shutdown; if a background close times out, the already-queued teardown still shuts the executors down after cleanup has been scheduled.

Event correlation remains synchronous where required. The internal ReplayController.captureReplay() contract now returns the active replay ID and sets it on the scope before deferred conversion. Terminating capture reaches the active strategy immediately so a main-thread crash cannot block it behind the looper. Deferred capture callbacks validate the replay generation, ID, and strategy before changing state, preventing an old replay from mutating a restarted one.

This is a breaking internal API change for hybrid SDKs that implement or bridge ReplayController. Returning the ID binds the triggering event or feedback to the exact sampled replay snapshot; falling back to the scope preserves an existing association when no new capture is accepted.

💡 Motivation and Context

Prepare Session Replay for public start and stop APIs without allowing calls from arbitrary threads to race, deadlock, or block Android lifecycle callbacks.

Always queuing startup also addresses the Session Replay initialization delay tracked in JAVA-656.

Refs JAVA-665
Refs JAVA-656

💚 How did you test it?

  • Ran ./gradlew spotlessApply apiDump.
  • Ran all Session Replay unit tests: 241 passed and 1 skipped.
  • Ran the full SentryAndroidTest suite.
  • Added focused coverage for lifecycle ordering, concurrent capture and stop, stale callbacks, crash termination, background-close timeouts, executor cleanup ordering, and deferred foreground initialization.

📝 Checklist

  • I added GH Issue ID & Linear ID
  • I added tests to verify the changes.
  • No new PII added or SDK only sends newly added PII if sendDefaultPII is enabled.
  • I updated the docs if needed.
  • I updated the wizard if needed.
  • Review from the native team if needed.
  • No breaking change or entry added to the changelog.
  • No breaking change for hybrid SDKs or communicated to hybrid SDKs.
  • Public API changes reviewed by another Mobile SDK team member or implemented according to the develop docs spec.

🔮 Next steps

  • Update hybrid SDK bridges for the ReplayController.captureReplay() return type.
  • Add the public Session Replay start and stop APIs in JAVA-325.
  • Document manual replay lifecycle control in JAVA-691.

Serialize replay lifecycle mutations on Android's main thread and keep replay cache cleanup ordered on the replay executor. Remove locks that could block lifecycle callbacks while preserving shutdown ordering.

Refs JAVA-665
Co-Authored-By: OpenAI Codex <noreply@openai.com>
@linear-code

linear-code Bot commented Aug 13, 2026

Copy link
Copy Markdown

JAVA-665

JAVA-656

@sentry

sentry Bot commented Aug 13, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

🔗 App Name App ID Version Configuration
SDK Size io.sentry.tests.size 8.53.0 (1) release

⚙️ sentry-android Build Distribution Settings

@github-actions

github-actions Bot commented Aug 13, 2026

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 319.04 ms 357.04 ms 38.00 ms
Size 0 B 0 B 0 B

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
fc5ccaf 270.49 ms 363.90 ms 93.41 ms
9d2f4e3 311.75 ms 357.80 ms 46.05 ms
694d587 312.37 ms 402.77 ms 90.41 ms
bb0ff41 317.76 ms 384.66 ms 66.90 ms
5b66efd 308.67 ms 363.85 ms 55.18 ms
c3ee041 310.64 ms 361.90 ms 51.26 ms
ad8da22 314.52 ms 352.47 ms 37.95 ms
44d18f5 334.50 ms 393.83 ms 59.33 ms
d15471f 303.49 ms 439.08 ms 135.59 ms
d15471f 342.08 ms 415.44 ms 73.35 ms

App size

Revision Plain With Sentry Diff
fc5ccaf 1.58 MiB 2.13 MiB 557.54 KiB
9d2f4e3 0 B 0 B 0 B
694d587 1.58 MiB 2.19 MiB 620.06 KiB
bb0ff41 0 B 0 B 0 B
5b66efd 1.58 MiB 2.13 MiB 559.07 KiB
c3ee041 0 B 0 B 0 B
ad8da22 1.58 MiB 2.29 MiB 719.83 KiB
44d18f5 0 B 0 B 0 B
d15471f 1.58 MiB 2.13 MiB 559.54 KiB
d15471f 1.58 MiB 2.13 MiB 559.54 KiB

Previous results on branch: romtsn/feat/java-665-replay-start-stop

Startup times

Revision Plain With Sentry Diff
5cdb0b8 311.75 ms 369.08 ms 57.33 ms
b208705 315.54 ms 354.54 ms 39.00 ms
f158a9a 352.96 ms 413.17 ms 60.21 ms

App size

Revision Plain With Sentry Diff
5cdb0b8 0 B 0 B 0 B
b208705 0 B 0 B 0 B
f158a9a 0 B 0 B 0 B

@romtsn
romtsn marked this pull request as ready for review August 13, 2026 13:12
Comment thread sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt Outdated
Comment thread sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt Outdated

@runningcode runningcode left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

left two thoughts!

Comment on lines +168 to 171
} catch (t: Throwable) {
release()
throw t
}

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

do you know why we catch and rethrow the throwable here instead of a finally ?

Suggested change
} catch (t: Throwable) {
release()
throw t
}
} finally {
release()
}

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

that's intentional - we don't want to release() on the happy path because we're gonna use the encoder right after and release after encoding a segment

Comment thread sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt Outdated
Comment thread sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt Outdated
Keep replay lifecycle state in one atomic value and serialize lifecycle transitions through the main looper. Return the replay ID synchronously so triggering events remain correlated while capture is deferred.

Refs JAVA-665
Refs JAVA-656
Co-Authored-By: Codex <noreply@openai.com>
romtsn and others added 2 commits August 17, 2026 09:48
Ensure terminating capture reaches the active replay strategy before a main-thread crash blocks the looper while flushing.

Co-Authored-By: Codex <noreply@openai.com>

@cursor cursor Bot left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Cursor Bugbot has reviewed your changes and found 1 potential issue.

Fix All in Cursor

❌ Bugbot Autofix is OFF. To automatically fix reported issues with cloud agents, enable autofix in the Cursor dashboard.

Want reviews to match your repository better? Bugbot Learning can learn team-specific rules from PR activity. A team admin can enable Learning in the Cursor dashboard.

Reviewed by Cursor Bugbot for commit e170e95. Configure here.

Comment thread sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayIntegration.kt Outdated
romtsn and others added 3 commits August 17, 2026 10:26
Use the configured thread checker for replay shutdown decisions so tests can control main-thread behavior through the existing abstraction.

Co-Authored-By: Codex <noreply@openai.com>
Keep executor cleanup queued behind main-thread replay teardown when a background close reaches its timeout. This prevents delayed teardown from submitting work to an executor that has already been shut down.

Co-Authored-By: Codex <noreply@openai.com>
Drain the main looper before asserting replay startup state now that lifecycle commands are always queued.

Co-Authored-By: Codex <noreply@openai.com>
@romtsn romtsn added the deep-dive PR needs a thorough review of design, behavior, and edge cases label Aug 17, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

deep-dive PR needs a thorough review of design, behavior, and edge cases

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants