linshenkx/prompt-optimizer · error · StorageError
Failed to perform atomic update after ${maxRetries} attempts
Error message
Failed to perform atomic update after ${maxRetries} attempts What it means
Terminal StorageError thrown by _performAtomicUpdateWithRetry after all retry attempts of the transactional update failed (it rethrows the lastError if one exists; this message only appears when no lastError was captured). The update path uses Dexie transactions with a lock and retries, degrading to _performSimpleUpdate on certain failures.
Source
Thrown at packages/core/src/services/storage/dexieStorageProvider.ts:239
// 如果是最后一次尝试或非事务错误,尝试降级到简单更新
if (attempt === maxRetries) {
console.warn(`All retries failed; falling back to simple update (${key})`);
try {
await this._performSimpleUpdate(key, updateFn);
console.log(`Fallback update succeeded (${key})`);
return;
} catch (fallbackError) {
console.error(`Fallback update also failed (${key}):`, fallbackError);
throw lastError; // 抛出原始错误
}
}
}
}
if (lastError) {
throw lastError
}
throw new StorageError(`Failed to perform atomic update after ${maxRetries} attempts`, 'write')
}
/**
* 简单更新(降级方案)
*/
private async _performSimpleUpdate<T>(
key: string,
updateFn: (currentValue: T | null) => T
): Promise<void> {
try {
// 读取当前值
const currentRecord = await this.db.storage.get(key);
const currentValue = currentRecord?.value
? JSON.parse(currentRecord.value) as T
: null;
// 应用更新函数
const newValue = updateFn(currentValue);View on GitHub (pinned to 3e677b1d9f)
Solutions
- Reduce concurrent writers: serialize updates per key or lower update frequency
- Avoid doing async non-Dexie work inside the update function passed to update() (causes PrematureCommitError)
- Catch this error and fall back to a non-transactional write, or surface it for a user retry
Example fix
// before
await provider.update('counter', fn);
// after
try { await provider.update('counter', fn); }
catch (e) {
if (e instanceof StorageError) {
await provider.setItem('counter', await readAndBump(provider)); // manual fallback
} else throw e;
} Defensive patterns
Strategy: retry
Validate before calling
null
Type guard
const isAtomicUpdateExhausted = (e: unknown): e is StorageError => e instanceof StorageError && /after \d+ attempts/.test(String((e as Error).message));
Try / catch
try { await provider.update(key, fn); } catch (e) { if (isAtomicUpdateExhausted(e)) { await sleep(250); await provider.update(key, fn); /* single retry with backoff */ } else throw e; } Prevention
- Serialize writes per key (queue/mutex) instead of racing concurrent update() calls
- Keep update callbacks free of non-Dexie async work
- Use exponential backoff if you must retry; more hammering worsens contention
When it happens
Trigger: Repeated transaction failures — e.g., persistent PrematureCommitError, lock contention where the promise-based lock keeps failing, or IndexedDB instability — exhausting maxRetries in update().
Common situations: High-frequency concurrent updates to the same key (counters, settings) in a multi-tab app; performing Dexie operations inside the transaction that trigger premature commits; browser storage corruption or quota pressure.
Related errors
AI-assisted analysis of linshenkx/prompt-optimizer@3e677b1d9f (2026-08-27).
Data as JSON: /api/errors/50bb195283122990.
Report an issue: GitHub.