fix(sqlite): bound WAL sidecar growth

This commit is contained in:
Peter Steinberger
2026-04-27 12:25:02 +01:00
parent bbfdb38e4e
commit 3bc29dd604
16 changed files with 234 additions and 14 deletions

View File

@@ -1,15 +1,26 @@
import path from "node:path";
import type { DatabaseSync } from "node:sqlite";
import { ensureDir, requireNodeSqlite } from "openclaw/plugin-sdk/memory-core-host-engine-storage";
import {
closeMemorySqliteWalMaintenance,
configureMemorySqliteWalMaintenance,
ensureDir,
requireNodeSqlite,
} from "openclaw/plugin-sdk/memory-core-host-engine-storage";
export function openMemoryDatabaseAtPath(dbPath: string, allowExtension: boolean): DatabaseSync {
const dir = path.dirname(dbPath);
ensureDir(dir);
const { DatabaseSync } = requireNodeSqlite();
const db = new DatabaseSync(dbPath, { allowExtension });
configureMemorySqliteWalMaintenance(db);
// busy_timeout is per-connection and resets to 0 on restart.
// Set it on every open so concurrent processes retry instead of
// failing immediately with SQLITE_BUSY.
db.exec("PRAGMA busy_timeout = 5000");
return db;
}
export function closeMemoryDatabase(db: DatabaseSync): void {
closeMemorySqliteWalMaintenance(db);
db.close();
}

View File

@@ -25,6 +25,7 @@ export type MemoryReadonlyRecoveryState = {
progress?: (update: MemorySyncProgressUpdate) => void;
}) => Promise<void>;
openDatabase: () => DatabaseSync;
closeDatabase: (db: DatabaseSync) => void;
resetVectorState: () => void;
ensureSchema: () => void;
readMeta: () => { vectorDims?: number } | undefined;
@@ -100,7 +101,7 @@ export async function runMemorySyncWithReadonlyRecovery(
state.readonlyRecoveryLastError = reason;
log.warn(`memory sync readonly handle detected; reopening sqlite connection`, { reason });
try {
state.db.close();
state.closeDatabase(state.db);
} catch {}
const previousVectorDims = state.vector.dims;
state.db = state.openDatabase();

View File

@@ -41,7 +41,7 @@ import {
type EmbeddingProviderRuntime,
} from "./embeddings.js";
import { runMemoryAtomicReindex } from "./manager-atomic-reindex.js";
import { openMemoryDatabaseAtPath } from "./manager-db.js";
import { closeMemoryDatabase, openMemoryDatabaseAtPath } from "./manager-db.js";
import {
applyMemoryFallbackProviderState,
resolveMemoryFallbackProviderRequest,
@@ -1204,8 +1204,8 @@ export abstract class MemoryManagerSyncOps {
this.writeMeta(meta);
this.pruneEmbeddingCacheIfNeeded?.();
this.db.close();
originalDb.close();
closeMemoryDatabase(this.db);
closeMemoryDatabase(originalDb);
originalDbClosed = true;
return meta;
},
@@ -1217,7 +1217,7 @@ export abstract class MemoryManagerSyncOps {
this.vector.dims = nextMeta?.vectorDims;
} catch (err) {
try {
this.db.close();
closeMemoryDatabase(this.db);
} catch {}
restoreOriginalState();
throw err;

View File

@@ -21,6 +21,7 @@ type ReadonlyRecoveryHarness = MemoryReadonlyRecoveryState & {
enqueueTargetedSessionSync: ReturnType<typeof vi.fn>;
runSync: ReturnType<typeof vi.fn>;
openDatabase: ReturnType<typeof vi.fn>;
closeDatabase: ReturnType<typeof vi.fn>;
resetVectorState: ReturnType<typeof vi.fn>;
ensureSchema: ReturnType<typeof vi.fn>;
readMeta: ReturnType<typeof vi.fn>;
@@ -80,6 +81,9 @@ describe("memory manager readonly recovery", () => {
enqueueTargetedSessionSync: vi.fn(async () => {}),
runSync: vi.fn(async (_params) => undefined) as ReadonlyRecoveryHarness["runSync"],
openDatabase: vi.fn(() => reopenedDb),
closeDatabase: vi.fn((db: DatabaseSync) => {
db.close();
}),
resetVectorState: vi.fn(function (this: ReadonlyRecoveryHarness) {
this.vector.dims = undefined;
this.vectorDegradedWriteWarningShown = false;

View File

@@ -36,6 +36,7 @@ import {
getOrCreateManagedCacheEntry,
resolveSingletonManagedCache,
} from "./manager-cache.js";
import { closeMemoryDatabase } from "./manager-db.js";
import { MemoryManagerEmbeddingOps } from "./manager-embedding-ops.js";
import {
resolveMemoryPrimaryProviderRequest,
@@ -694,6 +695,7 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
},
runSync: (nextParams) => this.runSync(nextParams),
openDatabase: () => this.openDatabase(),
closeDatabase: (db) => closeMemoryDatabase(db),
resetVectorState: () => this.resetVectorState(),
ensureSchema: () => this.ensureSchema(),
readMeta: () => this.readMeta() ?? undefined,
@@ -862,7 +864,7 @@ export class MemoryIndexManager extends MemoryManagerEmbeddingOps implements Mem
this.sessionUnsubscribe = null;
}
await awaitPendingManagerWork({ pendingSync, pendingProviderInit });
this.db.close();
closeMemoryDatabase(this.db);
INDEX_CACHE.delete(this.cacheKey);
}
}