Skip to content

Commit 018d279

Browse files
Alix-007steipete
authored andcommitted
fix(memory): clean rollback-journal reindex temp sidecar on NFS stores
cleanupAgedMemoryReindexTempFiles only removed WAL sidecars (-wal/-shm) of orphaned reindex temp DBs. On NFS-backed stores configureMemorySqliteWalMaintenance -> requireRollbackJournalMode forces journal_mode=DELETE, so the reindex temp DB uses a rollback journal; a hard crash leaves an orphaned .tmp-<uuid>-journal that leaked forever (cleanup neither deleted nor even discovered it). Add -journal to both the delete set (memoryIndexFileSuffixes) and the discovery set (reindexTempEntrySuffixes), with regression tests for the temp-plus-journal and stranded-journal cases.
1 parent a09f6b1 commit 018d279

2 files changed

Lines changed: 50 additions & 2 deletions

File tree

extensions/memory-core/src/memory/manager-db-probe.test.ts

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -121,6 +121,54 @@ describe("openMemoryDatabaseAtPath readOnly probe", () => {
121121
await expectPathMissing(`${orphanBase}-shm`);
122122
});
123123

124+
it("removes aged orphan reindex temp files including the rollback-journal sidecar", async () => {
125+
const dbPath = path.join(fixtureRoot, `case-${caseId++}`, "index.sqlite");
126+
const dir = path.dirname(dbPath);
127+
await fs.mkdir(dir, { recursive: true });
128+
const seed = new DatabaseSync(dbPath);
129+
seed.exec("CREATE TABLE IF NOT EXISTS meta(key TEXT PRIMARY KEY, value TEXT)");
130+
seed.close();
131+
132+
// NFS-backed stores keep journal_mode=DELETE, so a hard crash during a
133+
// reindex leaves a rollback-journal sidecar (.tmp-<uuid>-journal) rather
134+
// than -wal/-shm. Cleanup must remove it alongside the temp main file.
135+
const orphanBase = `${dbPath}.tmp-22222222-3333-4444-5555-666666666666`;
136+
for (const suffix of ["", "-journal"]) {
137+
const filePath = `${orphanBase}${suffix}`;
138+
await fs.writeFile(filePath, "orphan");
139+
const old = new Date(Date.now() - 48 * 60 * 60_000);
140+
await fs.utimes(filePath, old, old);
141+
}
142+
143+
const db = openMemoryDatabaseAtPath(dbPath, false);
144+
db.close();
145+
146+
await expectPathMissing(orphanBase);
147+
await expectPathMissing(`${orphanBase}-journal`);
148+
});
149+
150+
it("removes a stranded rollback-journal sidecar whose temp main file is already gone", async () => {
151+
const dbPath = path.join(fixtureRoot, `case-${caseId++}`, "index.sqlite");
152+
const dir = path.dirname(dbPath);
153+
await fs.mkdir(dir, { recursive: true });
154+
const seed = new DatabaseSync(dbPath);
155+
seed.exec("CREATE TABLE IF NOT EXISTS meta(key TEXT PRIMARY KEY, value TEXT)");
156+
seed.close();
157+
158+
// Only the rollback journal survives (its temp main file was already
159+
// removed). The discovery set must still recognize the orphan by its
160+
// -journal suffix, otherwise it leaks forever.
161+
const strandedJournal = `${dbPath}.tmp-33333333-4444-5555-6666-777777777777-journal`;
162+
await fs.writeFile(strandedJournal, "stranded journal");
163+
const old = new Date(Date.now() - 48 * 60 * 60_000);
164+
await fs.utimes(strandedJournal, old, old);
165+
166+
const db = openMemoryDatabaseAtPath(dbPath, false);
167+
db.close();
168+
169+
await expectPathMissing(strandedJournal);
170+
});
171+
124172
it("keeps young reindex temp files during live database startup", async () => {
125173
const dbPath = path.join(fixtureRoot, `case-${caseId++}`, "index.sqlite");
126174
const dir = path.dirname(dbPath);

extensions/memory-core/src/memory/manager-db.ts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -19,8 +19,8 @@ import {
1919
// own a young temp DB without losing its in-flight rebuild.
2020
const reindexTempFileWithoutLockMinAgeMs = 24 * 60 * 60_000;
2121
const reindexTempUuidPattern = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/;
22-
const memoryIndexFileSuffixes = ["", "-wal", "-shm"] as const;
23-
const reindexTempEntrySuffixes = ["-wal", "-shm", ""] as const;
22+
const memoryIndexFileSuffixes = ["", "-wal", "-shm", "-journal"] as const;
23+
const reindexTempEntrySuffixes = ["-wal", "-shm", "-journal", ""] as const;
2424

2525
function resolveReindexTempBaseName(dbBaseName: string, entryName: string): string | undefined {
2626
for (const suffix of reindexTempEntrySuffixes) {

0 commit comments

Comments
 (0)