-
Notifications
You must be signed in to change notification settings - Fork 96
Classify SQLite disk-pressure errors and stop retrying them #816
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
3febbe8
5de9702
2c1c4e8
d7abb2c
994f2ee
c78c578
5378d4d
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -52,6 +52,16 @@ const METHOD = { | |
| // Max number of retries for failed storage operations | ||
| const MAX_STORAGE_OPERATION_RETRY_ATTEMPTS = 5; | ||
|
|
||
| /** Minimum interval between disk-pressure alerts. One disk-pressure burst fails every queued operation | ||
| * with the identical error, so per-operation logging would amplify the very storm it reports. */ | ||
| const DISK_PRESSURE_LOG_INTERVAL_MS = 60000; | ||
| let lastDiskPressureLogTime = 0; | ||
|
|
||
| /** Test-only: clears the disk-pressure log throttle so each test observes its own alert. */ | ||
| function resetDiskPressureLogThrottle(): void { | ||
| lastDiskPressureLogTime = 0; | ||
| } | ||
|
|
||
| type OnyxMethod = ValueOf<typeof METHOD>; | ||
|
|
||
| // Key/value store of Onyx key and arrays of values to merge | ||
|
|
@@ -783,6 +793,9 @@ function reportStorageQuota(error?: Error): Promise<void> { | |
| * - CAPACITY: evicts the least recently accessed evictable key and retries, under a session-level | ||
| * circuit breaker (see lib/StorageCircuitBreaker.ts) that halts the loop once eviction stops making | ||
| * progress or failures storm — the per-operation budget alone cannot stop a session-wide storm. | ||
| * - DISK_PRESSURE: the device disk itself is full (or the database files are unreadable), so neither | ||
| * retries nor in-DB eviction can free space — the write is dropped (cache stays authoritative) with | ||
| * a single throttled alert + quota snapshot per burst. | ||
| * - UNKNOWN: the provider couldn't classify it — log the full error shape (name + message + | ||
| * provider) once so it's visible, then bounded retry without eviction. | ||
| */ | ||
|
|
@@ -806,6 +819,19 @@ function retryOperation<TMethod extends RetriableOnyxOperation>( | |
| return Promise.resolve(); | ||
| } | ||
|
|
||
| // DISK_PRESSURE: the device disk is full, so neither retries nor eviction can succeed until the OS | ||
| // frees space. Drop the write (cache stays authoritative) and log one alert + quota snapshot per | ||
| // interval — the snapshot's free-disk bytes let telemetry confirm (or rule out) disk pressure. | ||
| if (errorClass === StorageErrorClass.DISK_PRESSURE) { | ||
| const now = Date.now(); | ||
| if (now - lastDiskPressureLogTime < DISK_PRESSURE_LOG_INTERVAL_MS) { | ||
| return Promise.resolve(); | ||
| } | ||
| lastDiskPressureLogTime = now; | ||
| Logger.logAlert(`Disk-pressure storage error; skipping retries. provider: ${Storage.getStorageProvider().name}. message: ${error?.message}. onyxMethod: ${onyxMethod.name}.`); | ||
| return reportStorageQuota(error); | ||
| } | ||
|
Comment on lines
+825
to
+833
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The goal is not to send so many logs to VL, right? This is why we introduced
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. They solve different problems. The circuit breaker stops the evict → retry loop for |
||
|
|
||
| Logger.logInfo( | ||
| `Failed to save to storage. Error: ${error}. class: ${errorClass}. onyxMethod: ${onyxMethod.name}. retryAttempt: ${currentRetryAttempt}/${MAX_STORAGE_OPERATION_RETRY_ATTEMPTS}`, | ||
| ); | ||
|
|
@@ -1816,6 +1842,7 @@ const OnyxUtils = { | |
| getCollectionDataAndSendAsObject, | ||
| remove, | ||
| reportStorageQuota, | ||
| resetDiskPressureLogThrottle, | ||
| retryOperation, | ||
| broadcastUpdate, | ||
| hasPendingMergeForKey, | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -15,6 +15,10 @@ const StorageErrorClass = { | |
| TRANSIENT: 'transient', | ||
| /** Quota exceeded / disk full. Owner: operation layer — evict and retry. */ | ||
| CAPACITY: 'capacity', | ||
| /** Filesystem-level failure around the database files (device disk full, or the files cannot be | ||
| * created/read). Owner: operation layer — skip retries and eviction (neither can free OS-level | ||
| * space) and log one throttled alert + quota snapshot per burst. */ | ||
| DISK_PRESSURE: 'diskPressure', | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. How this is different from
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. their both related to disk full but |
||
| /** Non-serializable payload. Never retriable — the same data will always fail. */ | ||
| INVALID_DATA: 'invalidData', | ||
| /** Backing-store corruption. Owner: connection layer — budgeted heal, then give up. */ | ||
|
|
||
Uh oh!
There was an error while loading. Please reload this page.