fix: guard executor shutdown in BaseCaptureStrategy.stop() (#5627)
* fix: guard executor shutdown in BaseCaptureStrategy.stop() Each start/stop cycle leaked one SentryReplayPersister-* thread because stop() reset delegated properties (segmentTimestamp, currentReplayId) whose setters dispatch to persistingExecutor, initialising the lazy — but stop() never shut it down. Replace the lazy delegate with an explicit nullable holder so the executor is only created when actually needed and can be detected at stop() time. Call shutdownNow() (non-blocking) rather than the blocking shutdown() to avoid ANRs when stop() runs on the main thread. Fixes #5564 * style: apply spotless formatting * refactor(replay): move persistingExecutor ownership to ReplayIntegration Move persistingExecutor out of BaseCaptureStrategy and into ReplayIntegration, passing it as a constructor argument to CaptureStrategy subclasses. Shut it down in ReplayIntegration.close() alongside replayExecutor so executor lifecycle is managed in one place. * Fix leak in ReplayIntegration due to persisting executor not being shut down Add the persistingExecutor argument to SessionCaptureStrategy and BufferCaptureStrategy constructor calls in tests, and add changelog entry. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Remove stray merge conflict marker from CHANGELOG.md Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Remove no-op leak test from SessionCaptureStrategyTest The test used a mocked executor that never spawned threads, so the thread-count assertion was always true regardless of the fix. The executor lifecycle is now owned by ReplayIntegration, not SessionCaptureStrategy, so the test belonged at the wrong layer. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Add executor leak regression test to ReplayIntegrationTest Uses real ScheduledThreadPoolExecutor threads so the test actually fails if the shutdown in close() is removed. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> * Use shutdownNow() for replay executors in close() to avoid ANR shutdown() calls awaitTermination() which blocks up to shutdownTimeoutMillis. Since close() can run on the main thread (via Sentry.close() from hybrid SDKs), this risks an ANR. shutdownNow() is non-blocking and sufficient at teardown. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com> --------- Co-authored-by: Roman Zavarnitsyn <rom4ek93@gmail.com> Co-authored-by: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
T
tsushanth committed
d06126055527212a23f245ea8640d20b61bb5cd2
Parent: 58b65f0
Committed by GitHub <noreply@github.com>
on 7/2/2026, 7:32:56 AM