getsentry / getsentry/sentry-java

Android Replay: BufferCaptureStrategy.stop() can persist replay state after deleting replay cache dir

Đang mở
#6,094 1 bình luận 0 reaction 0 người được giao Xem trên GitHub
Android Bug Platform: Java Replays
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ả

### Problem Statement

Android Session Replay can throw an internal error during replay teardown:

```text
Failed to execute task CaptureStrategy.runInBackground
java.io.IOException: No such file or directory
...
at io.sentry.android.replay.ReplayCache.persistSegmentValues
...
at io.sentry.android.replay.capture.BufferCaptureStrategy.stop
```

This appears to happen when stopping a buffering replay.

### Likely root cause

`BufferCaptureStrategy.stop()` currently deletes the replay cache directory and then updates replay state in the same task:

- delete replay cache dir
- set `currentSegment = -1`

The `currentSegment` setter persists state through `ReplayCache.persistSegmentValues(...)`.

On the replay worker thread, that persistence runs inline, so it can try to recreate/write `.ongoing_segment` after the replay directory has already been deleted.

That leads to:

```text
java.io.IOException: No such file or directory
at java.io.File.createNewFile
at io.sentry.android.replay.ReplayCache.persistSegmentValues
```

### Why this matters

This is at least noisy internal error logging, but it may also contribute to brittle replay lifecycle behavior in manual/custom flows that use combinations of:

- `flush()`
- `pause()`
- `stop()`
- `startBuffering()`

In our investigation, this showed up while testing a custom User Feedback + Replay workaround, where replay behavior was already timing-sensitive.

### Relevant code path

Conceptually, the flow is:

- `BufferCaptureStrategy.stop()`
- `FileUtils.deleteRecursively(replayCacheDir)`
- `currentSegment = -1`
- `persistSegmentValues(...)`
- `ongoingSegmentFile?.createNewFile()`
- parent folder no longer exists

### Expected behavior

Stopping replay should not try to persist replay state into a directory that has already been deleted.

### Actual behavior

Replay teardown logs `No such file or directory` from `ReplayCache.persistSegmentValues`.

### Suggested fix direction

Any of these would likely resolve it:

- clear/persist mutable state before deleting the replay directory
- mark the cache closed before state mutation can persist
- guard `persistSegmentValues` against a missing/deleted parent directory during teardown
- avoid persisting teardown sentinel values like `currentSegment = -1` once shutdown has begun

### Customer-visible context

This was observed during a real customer reproduction involving Android Session Replay plus custom in-app User Feedback, where the workaround flow exercised replay stop/restart behavior repeatedly.

### Logs

```text
Failed to execute task CaptureStrategy.runInBackground
java.io.IOException: No such file or directory
at java.io.UnixFileSystem.createFileExclusively0(Native Method)
at java.io.UnixFileSystem.createFileExclusively(UnixFileSystem.java:373)
at java.io.File.createNewFile(File.java:1022)
at io.sentry.android.replay.ReplayCache.persistSegmentValues$sentry_android_replay_release(ReplayCache.kt:290)
at io.sentry.android.replay.capture.BaseCaptureStrategy$special$$inlined$persistableAtomic$default$2$2.invoke(BaseCaptureStrategy.kt:240)
at io.sentry.android.replay.capture.BaseCaptureStrategy$special$$inlined$persistableAtomic$default$2.runInBackground(BaseCaptureStrategy.kt:227)
at io.sentry.android.replay.capture.BaseCaptureStrategy$special$$inlined$persistableAtomic$default$2.setValue(BaseCaptureStrategy.kt:239)
at io.sentry.android.replay.capture.BaseCaptureStrategy.setCurrentSegment(BaseCaptureStrategy.kt:88)
at io.sentry.android.replay.capture.BufferCaptureStrategy.stop$lambda$0(BufferCaptureStrategy.kt:84)
```

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

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

Hướng nghiên cứu

Start with BufferCaptureStrategy.kt and ReplayCache.kt, following BufferCaptureStrategy.stop() through the currentSegment setter and persistSegmentValues(). Reproduce the buffering replay teardown path, especially after deleting the replay cache directory, and verify that stopping replay no longer logs the No such file or directory error.

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

Đánh giá

Công nghệ
android, kotlin
Lĩnh vực
mobile-dev
Loại issue
Lỗi
Độ khó
3/5
Thời gian dự kiến
1-2 ngày
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Khá rõ ràng
Mức phù hợp với người mới
70/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.