fix: [SDK-5065] bound the crash-record cache on iOS - #1725
Draft
abdulraqeeb33 wants to merge 1 commit into
Draft
Conversation
FileLogStore had no retention. save() enforced no size limit, listReadable had only the lower minAgeMillis gate with no ceiling, deleteUnrecognizedEntries reaped only .otlp.tmp and never touched owned records at any age, and there was no count or byte cap anywhere. A record that fails to upload was therefore re-read and re-POSTed on every launch indefinitely, with the directory growing until the OS reclaimed the cache. Android hit the same defect when OpenTelemetry's disk-buffering was removed and rebuilt the policy; this adopts that policy rather than reimplementing it. The decisions come from CrashRetention in the shared module, so both platforms reclaim identically and the rules stay unit-tested in one place. This file keeps only the I/O: snapshot the directory, apply what the selectors return. Retention now runs on all three paths. save() refuses oversized payloads and trims after a write, keeping the record it just wrote. listReadable reclaims before materializing payloads, so an over-cap backlog is never fully loaded. deleteUnrecognizedEntries reclaims too, since it is the only scan that runs when remote logging is disabled and otherwise a directory nothing reads would never be bounded. Foreign-file handling is deliberately left as it was, narrower than Android's shared selector: iOS never ran the OpenTelemetry pipeline, so there is no legacy format sharing this directory, and files we did not write are not ours to assume about. Depends on the CrashRetention API landing in the KMP submodule; the pin bump comes with that merge. Co-authored-by: Cursor <cursoragent@cursor.com>
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Description
One Line Summary
Bounds the iOS crash-record cache — age ceiling, count/byte caps, write-size limit — using the retention policy shared from the KMP module.
Relates to SDK-5065.
Important
Draft — depends on OneSignal-KMP-SDK#20.
CrashRetentiondoes not exist in the pinned submodule yet. This needs that PR merged and theOneSignal-KMP-SDKpin bumped before it can build in CI.The bug
FileLogStorehas no retention at all:save()enforces no size limitlistReadable()has only the lowerminAgeMillisgate — no ceilingdeleteUnrecognizedEntries()reaps only.otlp.tmp, never owned records at any ageSo a crash record that fails to upload is re-read and re-POSTed on every launch, indefinitely, and the directory grows unbounded. The path is under
.cachesDirectory, so iOS reclaims the disk under pressure — but the repeated re-upload is unmitigated, and a stuck record keeps being sent until the OS happens to purge.This is the same defect Android hit when OpenTelemetry's
disk-bufferingwas removed and its retention went with it. Android rebuilt the policy over several review rounds. Rather than reimplement it here and let the two drift, the policy moved tocommonMainand this adopts it.What changed
Decisions come from
CrashRetention; this file keeps only the I/O — snapshot the directory intoCrashDirEntrys, apply what the selectors return.Retention runs on all three paths, which is the part Android got wrong first:
save()listReadable()deleteUnrecognizedEntries()Deliberately unchanged: foreign-file policy
The shared
selectUnrecognizedreaps any non-owned file. iOS stays narrower and keeps reaping only its own.otlp.tmp. iOS never ran the OpenTelemetry pipeline, so there is no legacy format sharing this directory, and files we did not write are not ours to assume about.testFileStoreDeletesOnlyInterruptedTemporaryWritespins that intent and still passes unchanged.Testing
FileLogStoreRetentionTests— 10 cases covering the write-size limit and its exact boundary, the age ceiling and the record just inside it, oldest-first eviction past the count cap, never evicting the just-written record, inherited over-cap backlogs reclaimed by both the read and cleanup paths, and the preserved foreign-file behavior.Verified these actually bite: reverted
FileLogStore.swiftto its pre-retention state and re-ran — 7 of 10 fail, with the 3 passes being the negative and boundary cases that should still hold.Full
OneSignalOSCoresuite: 112 tests, 0 failures on iPhone 17 Pro simulator.The policy decisions themselves are unit-tested in the shared module (20 cases, green on both
iosSimulatorArm64and Android JVM).