mirror of
https://github.com/openclaw/openclaw.git
synced 2026-04-19 11:28:38 +00:00
refactor(config): dedupe session store save error handling
This commit is contained in:
@@ -391,16 +391,12 @@ async function saveSessionStoreUnlocked(
|
|||||||
const removedSessionFiles = new Map<string, string | undefined>();
|
const removedSessionFiles = new Map<string, string | undefined>();
|
||||||
const pruned = pruneStaleEntries(store, maintenance.pruneAfterMs, {
|
const pruned = pruneStaleEntries(store, maintenance.pruneAfterMs, {
|
||||||
onPruned: ({ entry }) => {
|
onPruned: ({ entry }) => {
|
||||||
if (!removedSessionFiles.has(entry.sessionId) || entry.sessionFile) {
|
rememberRemovedSessionFile(removedSessionFiles, entry);
|
||||||
removedSessionFiles.set(entry.sessionId, entry.sessionFile);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const capped = capEntryCount(store, maintenance.maxEntries, {
|
const capped = capEntryCount(store, maintenance.maxEntries, {
|
||||||
onCapped: ({ entry }) => {
|
onCapped: ({ entry }) => {
|
||||||
if (!removedSessionFiles.has(entry.sessionId) || entry.sessionFile) {
|
rememberRemovedSessionFile(removedSessionFiles, entry);
|
||||||
removedSessionFiles.set(entry.sessionId, entry.sessionFile);
|
|
||||||
}
|
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
const archivedDirs = new Set<string>();
|
const archivedDirs = new Set<string>();
|
||||||
@@ -474,14 +470,10 @@ async function saveSessionStoreUnlocked(
|
|||||||
if (process.platform === "win32") {
|
if (process.platform === "win32") {
|
||||||
for (let i = 0; i < 5; i++) {
|
for (let i = 0; i < 5; i++) {
|
||||||
try {
|
try {
|
||||||
await writeTextAtomic(storePath, json, { mode: 0o600 });
|
await writeSessionStoreAtomic({ storePath, store, serialized: json });
|
||||||
updateSessionStoreWriteCaches({ storePath, store, serialized: json });
|
|
||||||
return;
|
return;
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const code =
|
const code = getErrorCode(err);
|
||||||
err && typeof err === "object" && "code" in err
|
|
||||||
? String((err as { code?: unknown }).code)
|
|
||||||
: null;
|
|
||||||
if (code === "ENOENT") {
|
if (code === "ENOENT") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -498,25 +490,17 @@ async function saveSessionStoreUnlocked(
|
|||||||
}
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
await writeTextAtomic(storePath, json, { mode: 0o600 });
|
await writeSessionStoreAtomic({ storePath, store, serialized: json });
|
||||||
updateSessionStoreWriteCaches({ storePath, store, serialized: json });
|
|
||||||
} catch (err) {
|
} catch (err) {
|
||||||
const code =
|
const code = getErrorCode(err);
|
||||||
err && typeof err === "object" && "code" in err
|
|
||||||
? String((err as { code?: unknown }).code)
|
|
||||||
: null;
|
|
||||||
|
|
||||||
if (code === "ENOENT") {
|
if (code === "ENOENT") {
|
||||||
// In tests the temp session-store directory may be deleted while writes are in-flight.
|
// In tests the temp session-store directory may be deleted while writes are in-flight.
|
||||||
// Best-effort: try a direct write (recreating the parent dir), otherwise ignore.
|
// Best-effort: try a direct write (recreating the parent dir), otherwise ignore.
|
||||||
try {
|
try {
|
||||||
await writeTextAtomic(storePath, json, { mode: 0o600 });
|
await writeSessionStoreAtomic({ storePath, store, serialized: json });
|
||||||
updateSessionStoreWriteCaches({ storePath, store, serialized: json });
|
|
||||||
} catch (err2) {
|
} catch (err2) {
|
||||||
const code2 =
|
const code2 = getErrorCode(err2);
|
||||||
err2 && typeof err2 === "object" && "code" in err2
|
|
||||||
? String((err2 as { code?: unknown }).code)
|
|
||||||
: null;
|
|
||||||
if (code2 === "ENOENT") {
|
if (code2 === "ENOENT") {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@@ -574,6 +558,51 @@ type SessionStoreLockQueue = {
|
|||||||
|
|
||||||
const LOCK_QUEUES = new Map<string, SessionStoreLockQueue>();
|
const LOCK_QUEUES = new Map<string, SessionStoreLockQueue>();
|
||||||
|
|
||||||
|
function getErrorCode(error: unknown): string | null {
|
||||||
|
if (!error || typeof error !== "object" || !("code" in error)) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return String((error as { code?: unknown }).code);
|
||||||
|
}
|
||||||
|
|
||||||
|
function rememberRemovedSessionFile(
|
||||||
|
removedSessionFiles: Map<string, string | undefined>,
|
||||||
|
entry: SessionEntry,
|
||||||
|
): void {
|
||||||
|
if (!removedSessionFiles.has(entry.sessionId) || entry.sessionFile) {
|
||||||
|
removedSessionFiles.set(entry.sessionId, entry.sessionFile);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async function writeSessionStoreAtomic(params: {
|
||||||
|
storePath: string;
|
||||||
|
store: Record<string, SessionEntry>;
|
||||||
|
serialized: string;
|
||||||
|
}): Promise<void> {
|
||||||
|
await writeTextAtomic(params.storePath, params.serialized, { mode: 0o600 });
|
||||||
|
updateSessionStoreWriteCaches({
|
||||||
|
storePath: params.storePath,
|
||||||
|
store: params.store,
|
||||||
|
serialized: params.serialized,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
async function persistResolvedSessionEntry(params: {
|
||||||
|
storePath: string;
|
||||||
|
store: Record<string, SessionEntry>;
|
||||||
|
resolved: ReturnType<typeof resolveStoreSessionEntry>;
|
||||||
|
next: SessionEntry;
|
||||||
|
}): Promise<SessionEntry> {
|
||||||
|
params.store[params.resolved.normalizedKey] = params.next;
|
||||||
|
for (const legacyKey of params.resolved.legacyKeys) {
|
||||||
|
delete params.store[legacyKey];
|
||||||
|
}
|
||||||
|
await saveSessionStoreUnlocked(params.storePath, params.store, {
|
||||||
|
activeSessionKey: params.resolved.normalizedKey,
|
||||||
|
});
|
||||||
|
return params.next;
|
||||||
|
}
|
||||||
|
|
||||||
function lockTimeoutError(storePath: string): Error {
|
function lockTimeoutError(storePath: string): Error {
|
||||||
return new Error(`timeout waiting for session store lock: ${storePath}`);
|
return new Error(`timeout waiting for session store lock: ${storePath}`);
|
||||||
}
|
}
|
||||||
@@ -694,14 +723,12 @@ export async function updateSessionStoreEntry(params: {
|
|||||||
return existing;
|
return existing;
|
||||||
}
|
}
|
||||||
const next = mergeSessionEntry(existing, patch);
|
const next = mergeSessionEntry(existing, patch);
|
||||||
store[resolved.normalizedKey] = next;
|
return await persistResolvedSessionEntry({
|
||||||
for (const legacyKey of resolved.legacyKeys) {
|
storePath,
|
||||||
delete store[legacyKey];
|
store,
|
||||||
}
|
resolved,
|
||||||
await saveSessionStoreUnlocked(storePath, store, {
|
next,
|
||||||
activeSessionKey: resolved.normalizedKey,
|
|
||||||
});
|
});
|
||||||
return next;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -825,13 +852,11 @@ export async function updateLastRoute(params: {
|
|||||||
existing,
|
existing,
|
||||||
metaPatch ? { ...basePatch, ...metaPatch } : basePatch,
|
metaPatch ? { ...basePatch, ...metaPatch } : basePatch,
|
||||||
);
|
);
|
||||||
store[resolved.normalizedKey] = next;
|
return await persistResolvedSessionEntry({
|
||||||
for (const legacyKey of resolved.legacyKeys) {
|
storePath,
|
||||||
delete store[legacyKey];
|
store,
|
||||||
}
|
resolved,
|
||||||
await saveSessionStoreUnlocked(storePath, store, {
|
next,
|
||||||
activeSessionKey: resolved.normalizedKey,
|
|
||||||
});
|
});
|
||||||
return next;
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user