Skip to content

Commit efee9c4

Browse files
committed
fix(lmdb): recover from corrupted LMDB database instead of SIGSEGV crash
lmdb.open() on a corrupted data.mdb causes a native SIGSEGV that try/catch cannot intercept, crashing phnode on startup. This affects users upgrading from older lmdb versions. The fix validates DB integrity in a subprocess before opening — if the child crashes, we delete the corrupt files and restore from the periodic JSON dump.
1 parent ebdf205 commit efee9c4

1 file changed

Lines changed: 86 additions & 1 deletion

File tree

src-node/lmdb.js

Lines changed: 86 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
const lmdb= require("lmdb");
22
const path= require("path");
33
const fs= require("fs");
4+
const { execFileSync } = require("child_process");
45
const NodeConnector = require("./node-connector");
56

67
const STORAGE_NODE_CONNECTOR = "ph_storage";
@@ -13,15 +14,99 @@ let changesToDumpAvailable = false;
1314

1415
let storageDB,
1516
dumpFileLocation;
17+
/**
18+
* Validates LMDB database integrity by attempting to open it in a child process.
19+
* If the DB is corrupted, lmdb.open() can SIGSEGV the process (a native crash that
20+
* try/catch cannot intercept). By running the check in a subprocess, we detect the
21+
* crash without taking down the main phnode process.
22+
*
23+
* @param {string} lmdbDir - Path to the storageDB directory
24+
* @returns {boolean} true if DB is valid or doesn't exist, false if corrupted
25+
*/
26+
function _validateDBIntegrity(lmdbDir) {
27+
const dataFile = path.join(lmdbDir, "data.mdb");
28+
if (!fs.existsSync(dataFile)) {
29+
return true; // No DB yet, fresh start
30+
}
31+
try {
32+
// Run a child process that opens the DB and reads a key.
33+
// If the DB is corrupt, lmdb.open() can SIGSEGV the process — a native crash that
34+
// try/catch cannot intercept. By running in a subprocess, we detect the crash safely.
35+
const script = `
36+
const lmdb = require("lmdb");
37+
const db = lmdb.open({ path: ${JSON.stringify(lmdbDir)}, compression: false, readOnly: true });
38+
db.getKeys({ limit: 1 }).next();
39+
db.close();
40+
`;
41+
execFileSync(process.execPath, ["-e", script], {
42+
timeout: 10000,
43+
stdio: "pipe",
44+
cwd: __dirname
45+
});
46+
return true;
47+
} catch (err) {
48+
console.error("storageDB: Database integrity check failed:", err.status, err.signal);
49+
return false;
50+
}
51+
}
52+
53+
function _deleteCorruptDBFiles(lmdbDir) {
54+
const dataFile = path.join(lmdbDir, "data.mdb");
55+
const lockFile = path.join(lmdbDir, "lock.mdb");
56+
try {
57+
if (fs.existsSync(dataFile)) {
58+
fs.unlinkSync(dataFile);
59+
console.log("storageDB: Deleted corrupted data.mdb");
60+
}
61+
if (fs.existsSync(lockFile)) {
62+
fs.unlinkSync(lockFile);
63+
console.log("storageDB: Deleted stale lock.mdb");
64+
}
65+
} catch (deleteErr) {
66+
console.error("storageDB: Failed to delete corrupted DB files:", deleteErr);
67+
}
68+
}
69+
70+
async function _restoreFromDump(lmdbDir) {
71+
const dumpFile = path.join(lmdbDir, "storageDBDump.json");
72+
if (!fs.existsSync(dumpFile)) {
73+
console.warn("storageDB: No dump file found to restore from. Starting with empty database.");
74+
return;
75+
}
76+
try {
77+
const dumpData = JSON.parse(fs.readFileSync(dumpFile, "utf8"));
78+
const keys = Object.keys(dumpData);
79+
for (const key of keys) {
80+
await storageDB.put(key, dumpData[key]);
81+
}
82+
await storageDB.flushed;
83+
console.log(`storageDB: Restored ${keys.length} keys from dump file.`);
84+
} catch (restoreErr) {
85+
console.error("storageDB: Failed to restore from dump file:", restoreErr);
86+
}
87+
}
88+
1689
async function openDB(lmdbDir) {
1790
// see LMDB api docs in https://www.npmjs.com/package/lmdb?activeTab=readme
1891
lmdbDir = path.join(lmdbDir, "storageDB");
92+
dumpFileLocation = path.join(lmdbDir, "storageDBDump.json");
93+
94+
const needsRecovery = !_validateDBIntegrity(lmdbDir);
95+
if (needsRecovery) {
96+
console.error("storageDB: Corrupt database detected, deleting and recreating from dump.");
97+
_deleteCorruptDBFiles(lmdbDir);
98+
}
99+
19100
storageDB = lmdb.open({
20101
path: lmdbDir,
21102
compression: false
22103
});
104+
105+
if (needsRecovery) {
106+
await _restoreFromDump(lmdbDir);
107+
}
108+
23109
console.log("storageDB location is :", lmdbDir);
24-
dumpFileLocation = path.join(lmdbDir, "storageDBDump.json");
25110
}
26111

27112
async function flushDB() {

0 commit comments

Comments
 (0)