11const lmdb = require ( "lmdb" ) ;
22const path = require ( "path" ) ;
33const fs = require ( "fs" ) ;
4+ const { execFileSync } = require ( "child_process" ) ;
45const NodeConnector = require ( "./node-connector" ) ;
56
67const STORAGE_NODE_CONNECTOR = "ph_storage" ;
@@ -13,15 +14,99 @@ let changesToDumpAvailable = false;
1314
1415let 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+
1689async 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
27112async function flushDB ( ) {
0 commit comments