getsentry / getsentry/sentry-java
Disk-persisted data can race during SDK re-init
- Lenguaje dominante
- Kotlin
- Estrellas
- 1.4k
- Forks
- 478
- Merge medio
- 2 d 23 h
- PR fusionados (30 d)
- 67
Descripción
During SDK re-init (`Sentry.init` while already enabled), previous and new SDK lifecycles can concurrently read/write the same on-disk paths under `cacheDirPath`. On restart, `Scopes.close(true)` shuts the old executor down asynchronously rather than blocking, so old flush/session/envelope work can still touch files while the new init writes the same locations.
Impact: corrupted or lost scope/options/session state used for ANR/exit enrichment and crash recovery; possible lost breadcrumbs.
## Why this races
- Re-init path closes previous scopes with `isRestarting=true`, then continues init immediately: [`Sentry.init`](https://github.com/getsentry/sentry-java/blob/main/sentry/src/main/java/io/sentry/Sentry.java) → [`Scopes.close(true)`](https://github.com/getsentry/sentry-java/blob/main/sentry/src/main/java/io/sentry/Scopes.java)
- On restart the old executor is closed via a submitted task (non-blocking), so pending disk work can still run briefly: [`Scopes.close(boolean)`](https://github.com/getsentry/sentry-java/blob/main/sentry/src/main/java/io/sentry/Scopes.java)
- New init then enqueues session move/finalize, options observers, and scope-cache reset on the **new** executor against the same cache paths: [`notifyOptionsObservers` / `movePreviousSession` / `finalizePreviousSession`](https://github.com/getsentry/sentry-java/blob/main/sentry/src/main/java/io/sentry/Sentry.java)
- Fixed filenames are not isolated per SDK lifecycle / run id
## Disk-backed surfaces that share paths across re-init
**Scope cache** (`.scope-cache/`, via [`PersistingScopeObserver`](https://github.com/getsentry/sentry-java/blob/main/sentry/src/main/java/io/sentry/cache/PersistingScopeObserver.java) + [`CacheUtils`](https://github.com/getsentry/sentry-java/blob/main/sentry/src/main/java/io/sentry/cache/CacheUtils.java)):
- `breadcrumbs.json` — single `QueueFile`; old executor may still add/sync while new init `resetCache()` clears/syncs the same file
- `user.json`, `tags.json`, `extras.json`, `contexts.json`, `request.json`, `level.json`, `fingerprint.json`, `transaction.json`, `trace.json`, `replay.json` — plain overwrite/`delete` of fixed names; no atomic replace
- New init explicitly `resetCache()` after options observers run, which collides with any late old-lifecycle flushes
**Options cache** (`.options-cache/`, via [`PersistingOptionsObserver`](https://github.com/getsentry/sentry-java/blob/main/sentry/src/main/java/io/sentry/cache/PersistingOptionsObserver.java) and Android [`PersistingOptionsCacheGenerationObserver`](https://github.com/getsentry/sentry-java/blob/main/sentry-android-core/src/main/java/io/sentry/android/core/PersistingOptionsCacheGenerationObserver.java)):
- `release.json`, `proguard-uuid.json`, `sdk-version.json`, `environment.json`, `dist.json`, `tags.json`, `replay-error-sample-rate.json`, `app-last-update-time.json`
- Written on every init via options observers; same fixed paths, non-atomic `FileOutputStream` overwrite
**Session / envelope cache** ([`EnvelopeCache`](https://github.com/getsentry/sentry-java/blob/main/sentry/src/main/java/io/sentry/cache/EnvelopeCache.java), Android [`AndroidEnvelopeCache`](https://github.com/getsentry/sentry-java/blob/main/sentry-android-core/src/main/java/io/sentry/android/core/cache/AndroidEnvelopeCache.java)):
- `session.json` / `previous_session.json` — move + rewrite on session start; previous-session finalize also reads/deletes these
- `*.envelope` offline cache — old client flush vs new client store/send against same dir
- crash markers: `last_crash`, `.sentry-native/last_crash`, `startup_crash` (outbox)
- Android ANR last-reported markers under cache dir
**Other fixed-path / shared-dir writers** (same `cacheDirPath`, lifecycle-overlapping on re-init):
- App-start profiling config file rewritten on init ([`Sentry.handleAppStartProfilingConfig`](https://github.com/getsentry/sentry-java/blob/main/sentry/src/main/java/io/sentry/Sentry.java))
- Profiling traces dir cleanup on init vs in-flight profile finalization
- Session Replay dirs (`replay_/`) under cache dir ([`ReplayCache`](https://github.com/getsentry/sentry-java/blob/main/sentry-android-replay/src/main/java/io/sentry/android/replay/ReplayCache.kt)); cleanup walks cache dir on integration start
- ANR profile `QueueFile`s under cache dir ([`AnrProfileManager`](https://github.com/getsentry/sentry-java/blob/main/sentry-android-core/src/main/java/io/sentry/android/core/anr/AnrProfileManager.java) / rotation helper already documents concurrent `QueueFile` risk)
## Highest-confidence race today
Scope persistence breadcrumbs: single shared `breadcrumbs.json` `QueueFile`, old executor still draining adds while new init clears it in `resetCache()`, with only executor ordering (not cross-lifecycle isolation) as the safety net.
## Related
- Closed perf work on scope persistence writes: https://github.com/getsentry/sentry-java/issues/5714
- Closed re-init executor timeout work: https://github.com/getsentry/sentry-java/issues/5715
- Closed “don’t wait when SDK is reinitialized”: https://github.com/getsentry/sentry-java/issues/3162
## Unknowns
- Whether this has been observed in production beyond code inspection / reasoning about re-init
- How often customer/integration re-init hits the overlapping-executor window in practice
Raised by Markus Hintersteiner during SDK re-init / disk-persistence discussion.
Requested by **markus.hintersteiner**.
--
[View Junior Session](https://junior-prod.sentry.dev/conversations/slack%3ACUCRT2B70%3A1786606836.172319) [[Sentry]](https://sentry.sentry.io/explore/conversations/slack%3ACUCRT2B70%3A1786606836.172319/?project=4510944073809921)
Guía de contribución
Línea de trabajo
Trace re-initialization from Sentry.init through Scopes.close(boolean), then inspect PersistingScopeObserver, CacheUtils, PersistingOptionsObserver, and EnvelopeCache. Focus first on the shared breadcrumbs.json QueueFile and the old executor's shutdown behavior; done should demonstrate that re-init cannot overlap disk operations or lose persisted scope, options, session, or envelope data.
Escrito por el modelo de indexación a partir del texto del issue.
Evaluación
- Stack tecnológico
- java, kotlin
- Área
- mobile, tooling
- Tipo de issue
- Error
- Dificultad
- 5/5
- Tiempo estimado
- Más de una semana
- Estado de actividad
- Tranquilo
- Claridad
- Bastante claro
- Aptitud para principiantes
- 35/100