ref(android): Confine replay lifecycle to main thread - #5965
Open
romtsn wants to merge 10 commits into
Open
Conversation
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>
📲 Install BuildsAndroid
|
Contributor
Performance metrics 🚀
|
| Revision | Plain | With Sentry | Diff |
|---|---|---|---|
| a21e537 | 319.04 ms | 357.04 ms | 38.00 ms |
| 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 |
|---|---|---|---|
| a21e537 | 0 B | 0 B | 0 B |
| 5cdb0b8 | 0 B | 0 B | 0 B |
| b208705 | 0 B | 0 B | 0 B |
| f158a9a | 0 B | 0 B | 0 B |
romtsn
marked this pull request as ready for review
August 13, 2026 13:12
romtsn
requested review from
0xadam-brown,
adinauer,
markushi and
runningcode
as code owners
August 13, 2026 13:12
runningcode
reviewed
Aug 14, 2026
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>
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>
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>
9 tasks
There was a problem hiding this comment.
Cursor Bugbot has reviewed your changes and found 1 potential issue.
❌ 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 d09bb70. Configure here.
Clear the scope replay ID when a queued capture discovers its replay was stopped or restarted. Preserve any newer replay ID by checking the expected ID before clearing. Refs JAVA-665 Co-Authored-By: Codex <noreply@openai.com>
runningcode
approved these changes
Aug 24, 2026
runningcode
left a comment
Contributor
There was a problem hiding this comment.
Nice! Thanks for addressing the PR comments!
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
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?
./gradlew spotlessApply apiDump.SentryAndroidTestsuite.📝 Checklist
sendDefaultPIIis enabled.🔮 Next steps
ReplayController.captureReplay()return type.