fix: prevent SQLite corruption by checkpointing WAL and closing DB on shutdown
The database was never properly closed on SIGINT/SIGTERM — WAL files were left in a partially-written state causing "database disk image is malformed" errors. Adds WAL checkpoint on close, synchronous=NORMAL pragma, SIGTERM handlers, and a store.close() method that flushes all pending writes before exit. Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
This commit is contained in:
@@ -122,6 +122,8 @@ export class CentralDatabase {
|
||||
|
||||
// Enable WAL mode for concurrent reader/writer access
|
||||
this.db.exec("PRAGMA journal_mode = WAL");
|
||||
// Ensure data reaches disk on commit (NORMAL is safe with WAL mode)
|
||||
this.db.exec("PRAGMA synchronous = NORMAL");
|
||||
// Enable foreign key enforcement
|
||||
this.db.exec("PRAGMA foreign_keys = ON");
|
||||
}
|
||||
@@ -144,8 +146,14 @@ export class CentralDatabase {
|
||||
|
||||
/**
|
||||
* Close the database connection.
|
||||
* Checkpoints the WAL first to ensure all writes are flushed to the main db file.
|
||||
*/
|
||||
close(): void {
|
||||
try {
|
||||
this.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
||||
} catch {
|
||||
// Best-effort: checkpoint failure is non-fatal
|
||||
}
|
||||
this.db.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -264,6 +264,8 @@ export class Database {
|
||||
|
||||
// Enable WAL mode for concurrent reader/writer access
|
||||
this.db.exec("PRAGMA journal_mode = WAL");
|
||||
// Ensure data reaches disk on commit (NORMAL is safe with WAL mode)
|
||||
this.db.exec("PRAGMA synchronous = NORMAL");
|
||||
// Enable foreign key enforcement
|
||||
this.db.exec("PRAGMA foreign_keys = ON");
|
||||
}
|
||||
@@ -368,10 +370,25 @@ export class Database {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checkpoint the WAL file back into the main database.
|
||||
* This reduces the risk of corruption from incomplete WAL writes
|
||||
* and keeps the WAL file from growing unbounded.
|
||||
*/
|
||||
checkpoint(): void {
|
||||
try {
|
||||
this.db.exec("PRAGMA wal_checkpoint(TRUNCATE)");
|
||||
} catch {
|
||||
// Best-effort: checkpoint failure is non-fatal
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Close the database connection.
|
||||
* Checkpoints the WAL first to ensure all writes are flushed to the main db file.
|
||||
*/
|
||||
close(): void {
|
||||
this.checkpoint();
|
||||
this.db.close();
|
||||
}
|
||||
|
||||
|
||||
@@ -1693,6 +1693,18 @@ export class TaskStore extends EventEmitter<TaskStoreEvents> {
|
||||
this.recentlyWritten.clear();
|
||||
}
|
||||
|
||||
/**
|
||||
* Gracefully shut down: stop watching and close the database connection.
|
||||
* Ensures WAL is checkpointed so no pending writes are lost.
|
||||
*/
|
||||
close(): void {
|
||||
this.stopWatching();
|
||||
if (this._db) {
|
||||
this._db.close();
|
||||
this._db = null;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Mark a file path as recently written by an in-process mutation
|
||||
* so the watcher will skip it.
|
||||
|
||||
Reference in New Issue
Block a user