getsentry / getsentry/sentry-java

Disk-persisted data can race during SDK re-init

Đang mở
#5,959 1 bình luận 0 reaction 0 người được giao Xem trên GitHub
Android Improvement Platform: Java Type: Bug
Ngôn ngữ chính
Kotlin
Star
1.4k
Fork
478
Merge trung bình
2 ngày 23 giờ
Pull request đã merge (30 ngày)
67

Mô tả

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)

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Hướng nghiên cứu

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.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
java, kotlin
Lĩnh vực
mobile, tooling
Loại issue
Lỗi
Độ khó
5/5
Thời gian dự kiến
Hơn một tuần
Mức độ hoạt động
Ít trao đổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
35/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.