From 317c88652924dadd2547f419b88b0acee5fa5f28 Mon Sep 17 00:00:00 2001 From: devilreef <86633411+devilr33f@users.noreply.github.com> Date: Fri, 16 Jan 2026 14:50:26 +0600 Subject: [PATCH] fix: serialize state writes with retry for EBUSY errors - Add write serialization to prevent concurrent writes - Retry with exponential backoff on Windows EBUSY - Use direct write instead of temp file + rename Co-Authored-By: Claude --- src/core/state-store.ts | 42 +++++++++++++++++++++++++++++++++-------- 1 file changed, 34 insertions(+), 8 deletions(-) diff --git a/src/core/state-store.ts b/src/core/state-store.ts index f4468b1..283c239 100644 --- a/src/core/state-store.ts +++ b/src/core/state-store.ts @@ -8,6 +8,8 @@ export class StateStore { private dirty = new Set() private persistTimer: NodeJS.Timeout | null = null private loaded = false + private writeInProgress = false + private pendingWrite = false async load(): Promise { if (this.loaded) { @@ -57,24 +59,48 @@ export class StateStore { this.persistTimer = null if (this.dirty.size === 0) { + this.writeInProgress = false return } + if (this.writeInProgress) { + this.pendingWrite = true + return + } + + this.writeInProgress = true + // Write full state, not just dirty keys (atomicity) const toSave: Record = {} for (const [key, value] of this.state.entries()) { toSave[key] = value } - try { - // Atomic write: temp file + rename - const tmpFile = `${STATE_FILE}.tmp` - await fs.writeFile(tmpFile, JSON.stringify(toSave, null, 2)) - await fs.rename(tmpFile, STATE_FILE) - this.dirty.clear() + const retries = 3 + const backoffMs = [100, 300, 500] + + for (let attempt = 0; attempt < retries; attempt++) { + try { + // Direct write without temp file (Windows EBUSY workaround) + await fs.writeFile(STATE_FILE, JSON.stringify(toSave, null, 2)) + this.dirty.clear() + break + } + catch (err) { + const error = err as NodeJS.ErrnoException + if (error.code === 'EBUSY' && attempt < retries - 1) { + await new Promise(resolve => setTimeout(resolve, backoffMs[attempt])) + continue + } + console.error('[StateStore] Failed to write state file:', err) + } } - catch (err) { - console.error('[StateStore] Failed to write state file:', err) + + this.writeInProgress = false + + if (this.pendingWrite) { + this.pendingWrite = false + await this.persist() } }