Skip to content

fix(replay): Skip buffer-mode replay capture when rate-limited (DART-313) - #5813

Merged
runningcode merged 5 commits into
mainfrom
no/replay-skip-encode-when-rate-limited
Jul 29, 2026
Merged

fix(replay): Skip buffer-mode replay capture when rate-limited (DART-313)#5813
runningcode merged 5 commits into
mainfrom
no/replay-skip-encode-when-rate-limited

Conversation

@runningcode

@runningcode runningcode commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

📜 Description

In buffer (on-error) session replay mode, the recorder intentionally keeps running while the SDK is rate-limited so the rolling buffer stays warm. However, when an error triggered captureReplay, the strategy still encoded the current segment and every buffered segment and handed the resulting envelopes to the transport — which then discarded them because of the active rate limit. That wasted CPU, disk I/O, and MediaMuxer file descriptors on replays that could never be sent.

This adds a guard at the top of BufferCaptureStrategy.captureReplay: if the Replay (or All) data category is rate-limited, we skip encoding and capturing entirely. This mirrors the guard session mode already applies via onRateLimitChanged/checkCanRecord. The buffer keeps rolling cheaply in the background and captures normally once the rate limit expires.

💡 Motivation and Context

Follow-up to the session replay resource-leak work in #5583 and #5607. Those fixed the MediaMuxer FD leak during encoding; this closes the remaining item called out in getsentry/sentry-dart#3528 (getsentry/sentry-dart#3528): while rate-limited, the Android SDK kept creating and encoding segments that were only going to be discarded.

💚 How did you test it?

Added a unit test (captureReplay does nothing when rate-limited) asserting that, even with a buffered segment present, no segment is sent to the transport and no replayId is written to the scope while rate-limited.

📝 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

Optionally extend the same guard to the pause and onConfigurationChanged encode paths in buffer mode if that churn proves significant.

…313)

In buffer (on-error) mode the recorder keeps running while rate-limited so
the rolling buffer stays warm, but capturing on an error still encoded the
current and buffered segments and handed them to the transport, which then
dropped them. That wasted CPU, I/O, and MediaMuxer file descriptors on
envelopes that could never be sent.

Bail out of BufferCaptureStrategy.captureReplay when the Replay (or All)
category is rate-limited, mirroring the guard session mode already applies.

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

linear-code Bot commented Jul 22, 2026

Copy link
Copy Markdown

DART-313

@sentry

sentry Bot commented Jul 22, 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

github-actions Bot commented Jul 22, 2026

Copy link
Copy Markdown
Contributor

Performance metrics 🚀

  Plain With Sentry Diff
Startup time 310.41 ms 355.90 ms 45.49 ms
Size 0 B 0 B 0 B

Baseline results on branch: main

Startup times

Revision Plain With Sentry Diff
05aa61d 326.06 ms 385.46 ms 59.40 ms
bb0ff41 315.84 ms 350.76 ms 34.92 ms
806307f 357.85 ms 424.64 ms 66.79 ms
d501a7e 307.33 ms 341.94 ms 34.61 ms
0ee65e9 321.06 ms 361.24 ms 40.18 ms
ed33deb 334.19 ms 362.30 ms 28.11 ms
9fbb112 401.87 ms 515.87 ms 114.00 ms
b8bd880 314.56 ms 336.50 ms 21.94 ms
5b1a06b 315.40 ms 353.33 ms 37.94 ms
6edfca2 316.43 ms 398.90 ms 82.46 ms

App size

Revision Plain With Sentry Diff
05aa61d 0 B 0 B 0 B
bb0ff41 0 B 0 B 0 B
806307f 1.58 MiB 2.10 MiB 533.42 KiB
d501a7e 0 B 0 B 0 B
0ee65e9 0 B 0 B 0 B
ed33deb 1.58 MiB 2.13 MiB 559.52 KiB
9fbb112 1.58 MiB 2.11 MiB 539.18 KiB
b8bd880 1.58 MiB 2.29 MiB 722.92 KiB
5b1a06b 0 B 0 B 0 B
6edfca2 1.58 MiB 2.13 MiB 559.07 KiB

Previous results on branch: no/replay-skip-encode-when-rate-limited

Startup times

Revision Plain With Sentry Diff
80c59fb 317.33 ms 342.69 ms 25.36 ms
a28aa98 321.85 ms 368.79 ms 46.94 ms

App size

Revision Plain With Sentry Diff
80c59fb 0 B 0 B 0 B
a28aa98 0 B 0 B 0 B

@runningcode
runningcode marked this pull request as ready for review July 24, 2026 11:05

@romtsn romtsn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

pre-approving, but 2 things I'd want to have addressed

runningcode and others added 2 commits July 28, 2026 17:08
…imited (DART-313)

Skipping the encode when rate-limited meant the segments never reached the
transport, so RateLimiter.filter never recorded them as lost. Replay drops in
buffer mode silently vanished from client reports.

Record a RATELIMIT_BACKOFF lost event for the Replay category at the bail-out,
and move the rate-limit check below the sampling and isTerminating guards so we
only report replays that would genuinely have been sent — a replay dropped by
onErrorSampleRate is not a rate-limit loss, and a terminating one is deferred to
the next launch rather than lost.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.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.

Reviewed by Cursor Bugbot for commit 91a65bb. Configure here.

Bailing out of BufferCaptureStrategy.captureReplay while rate-limited left
isTerminating unset, so ReplayIntegration's unconditional convert() still
swapped in a SessionCaptureStrategy. That discarded the rolling buffer, and
the next recorded frame then hit checkCanRecord(), which pauses session mode
when rate-limited and encodes a segment on the way out - exactly the work the
bail-out was meant to avoid. It also left recording paused for the rest of the
rate-limit window, contradicting onRateLimitChanged, which deliberately keeps
buffer mode running.

Stay in buffer mode while rate-limited so the buffer keeps rolling and the
next error after the limit expires can send a complete replay.

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

@romtsn romtsn left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

double-checked everything, it all makes sense, approving!

@runningcode
runningcode enabled auto-merge (squash) July 29, 2026 08:35
@runningcode
runningcode merged commit 02b196f into main Jul 29, 2026
70 of 71 checks passed
@runningcode
runningcode deleted the no/replay-skip-encode-when-rate-limited branch July 29, 2026 08:36
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.

2 participants