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 <noreply@anthropic.com>
This commit is contained in:
parent
58ef04fa68
commit
317c886529
1 changed files with 34 additions and 8 deletions
|
|
@ -8,6 +8,8 @@ export class StateStore {
|
||||||
private dirty = new Set<string>()
|
private dirty = new Set<string>()
|
||||||
private persistTimer: NodeJS.Timeout | null = null
|
private persistTimer: NodeJS.Timeout | null = null
|
||||||
private loaded = false
|
private loaded = false
|
||||||
|
private writeInProgress = false
|
||||||
|
private pendingWrite = false
|
||||||
|
|
||||||
async load(): Promise<void> {
|
async load(): Promise<void> {
|
||||||
if (this.loaded) {
|
if (this.loaded) {
|
||||||
|
|
@ -57,24 +59,48 @@ export class StateStore {
|
||||||
this.persistTimer = null
|
this.persistTimer = null
|
||||||
|
|
||||||
if (this.dirty.size === 0) {
|
if (this.dirty.size === 0) {
|
||||||
|
this.writeInProgress = false
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (this.writeInProgress) {
|
||||||
|
this.pendingWrite = true
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
this.writeInProgress = true
|
||||||
|
|
||||||
// Write full state, not just dirty keys (atomicity)
|
// Write full state, not just dirty keys (atomicity)
|
||||||
const toSave: Record<string, unknown> = {}
|
const toSave: Record<string, unknown> = {}
|
||||||
for (const [key, value] of this.state.entries()) {
|
for (const [key, value] of this.state.entries()) {
|
||||||
toSave[key] = value
|
toSave[key] = value
|
||||||
}
|
}
|
||||||
|
|
||||||
try {
|
const retries = 3
|
||||||
// Atomic write: temp file + rename
|
const backoffMs = [100, 300, 500]
|
||||||
const tmpFile = `${STATE_FILE}.tmp`
|
|
||||||
await fs.writeFile(tmpFile, JSON.stringify(toSave, null, 2))
|
for (let attempt = 0; attempt < retries; attempt++) {
|
||||||
await fs.rename(tmpFile, STATE_FILE)
|
try {
|
||||||
this.dirty.clear()
|
// 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()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue