Skip to content

fix(replay): Dispatch session-deadline stop off the replay worker thread#5826

Draft
runningcode wants to merge 2 commits into
mainfrom
no/fix-replay-deadline-stop-anr
Draft

fix(replay): Dispatch session-deadline stop off the replay worker thread#5826
runningcode wants to merge 2 commits into
mainfrom
no/fix-replay-deadline-stop-anr

Conversation

@runningcode

Copy link
Copy Markdown
Contributor

📜 Description

When a session replay reaches its duration deadline (default 1h), the resulting stop() was invoked inline from the replay worker thread, inside the add_frame task in SessionCaptureStrategy.onScreenshotRecorded. Because ReplayExecutorService.submit() runs tasks synchronously when the caller is already on a SentryReplayIntegration-* thread, stop() encoded the final video segment and recursively deleted the replay cache while holding the replay lifecycle lock (AutoClosableReentrantLock).

A foreground start() (LifecycleWatcher.onForegroundstartSession) runs on the main thread and must acquire that same lock, so it parked long enough to trigger a background ANR.

This change dispatches the deadline stop() through options.executorService (a non-worker thread). stop() then submits the segment encoding asynchronously onto the single-threaded replay executor (preserving ordering), and the lifecycle lock is held only for the fast state transition — matching the existing timer-executor endSession stop path.

💡 Motivation and Context

Fixes the background ANR reported in getsentry/sentry-dart#3556 (Linear: DART-323), whose stack shows the main thread parked on the replay lifecycle lock in ReplayIntegration.start() on foreground. The deadline stop re-entering the lifecycle lock from the worker thread is the only path where the segment encode runs synchronously under the lock; every other start/stop/pause/resume caller already submits the encode asynchronously.

💚 How did you test it?

Added a regression test in SessionCaptureStrategyTest using a deferred executor that asserts the deadline stop() is dispatched off the calling thread (not run inline) and only runs once the executor drains. Updated the existing deadline test's fixture to use an immediate executor so it still exercises the dispatched stop.

📝 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.

🔮 Next steps

None.

runningcode and others added 2 commits July 23, 2026 14:16
…ead (DART-323)

When a session replay reaches its duration deadline, the stop was invoked
inline from the replay worker thread inside the frame-processing task. Because
ReplayExecutorService.submit() runs tasks synchronously when already on that
thread, stop() encoded the final segment and deleted the replay cache while
holding the replay lifecycle lock. A foreground start() on the main thread then
parked on that lock long enough to trigger a background ANR.

Dispatch the deadline stop through options.executorService so the segment
encoding is enqueued asynchronously and the lifecycle lock is only held for the
fast state transition, matching the existing timer-executor stop path.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
@sentry

sentry Bot commented Jul 23, 2026

Copy link
Copy Markdown

📲 Install Builds

Android

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

⚙️ sentry-android Build Distribution Settings

@github-actions

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 338.21 ms 382.47 ms 44.25 ms
Size 0 B 0 B 0 B

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
22f4345 325.23 ms 454.66 ms 129.43 ms
22f4345 312.78 ms 347.40 ms 34.62 ms
8558cac 306.16 ms 355.24 ms 49.09 ms
bb0ff41 317.76 ms 384.66 ms 66.90 ms
7c1a728 289.46 ms 368.15 ms 78.69 ms
d501a7e 314.55 ms 343.34 ms 28.79 ms
4fc476b 280.63 ms 363.04 ms 82.42 ms
6727e14 337.22 ms 373.94 ms 36.71 ms
ae7fed0 293.84 ms 380.22 ms 86.38 ms
ee747ae 400.46 ms 423.61 ms 23.15 ms

App size

Revision Plain With Sentry Diff
22f4345 1.58 MiB 2.29 MiB 719.83 KiB
22f4345 1.58 MiB 2.29 MiB 719.83 KiB
8558cac 0 B 0 B 0 B
bb0ff41 0 B 0 B 0 B
7c1a728 0 B 0 B 0 B
d501a7e 0 B 0 B 0 B
4fc476b 0 B 0 B 0 B
6727e14 1.58 MiB 2.28 MiB 718.64 KiB
ae7fed0 1.58 MiB 2.12 MiB 551.77 KiB
ee747ae 1.58 MiB 2.10 MiB 530.95 KiB

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant