From 9055661b5142097f53be177819170c9772503361 Mon Sep 17 00:00:00 2001 From: Zimeng Xiong Date: Sat, 22 Nov 2025 21:59:18 -0800 Subject: [PATCH] make async database integrity check --- backend/src/index.ts | 43 ++++++++++++++++++++------------ backend/src/workers/db-verify.js | 18 +++++++++++++ 2 files changed, 45 insertions(+), 16 deletions(-) create mode 100644 backend/src/workers/db-verify.js diff --git a/backend/src/index.ts b/backend/src/index.ts index fa47cc0..c63e25c 100644 --- a/backend/src/index.ts +++ b/backend/src/index.ts @@ -5,6 +5,7 @@ import path from "path"; import fs from "fs"; import { createServer } from "http"; import { Server } from "socket.io"; +import { Worker } from "worker_threads"; import multer from "multer"; import archiver from "archiver"; import Database from "better-sqlite3"; @@ -125,21 +126,31 @@ const respondWithValidationErrors = ( }); }; -const runIntegrityCheck = (filePath: string): boolean => { - let dbInstance: Database.Database | undefined; - try { - dbInstance = new Database(filePath, { - readonly: true, - fileMustExist: true, +// Non-blocking CPU check using worker threads +const verifyDatabaseIntegrityAsync = (filePath: string): Promise => { + return new Promise((resolve) => { + const worker = new Worker( + path.resolve(__dirname, "./workers/db-verify.js"), + { + workerData: { filePath }, + } + ); + + worker.on("message", (isValid: boolean) => resolve(isValid)); + worker.on("error", (err) => { + console.error("Worker error:", err); + resolve(false); }); - const result = dbInstance.prepare("PRAGMA integrity_check;").get(); - return result?.integrity_check === "ok"; - } catch (error) { - console.error("Integrity check failed:", error); - return false; - } finally { - dbInstance?.close(); - } + worker.on("exit", (code) => { + if (code !== 0) resolve(false); + }); + + // Kill worker if it takes too long (DoS protection) + setTimeout(() => { + worker.terminate(); + resolve(false); + }, 10000); // 10 second timeout + }); }; const removeFileIfExists = (filePath?: string) => { @@ -645,7 +656,7 @@ app.post("/import/sqlite/verify", upload.single("db"), async (req, res) => { } const stagedPath = req.file.path; - const isValid = runIntegrityCheck(stagedPath); + const isValid = await verifyDatabaseIntegrityAsync(stagedPath); removeFileIfExists(stagedPath); if (!isValid) { @@ -684,7 +695,7 @@ app.post("/import/sqlite", upload.single("db"), async (req, res) => { return res.status(500).json({ error: "Failed to stage uploaded file" }); } - const isValid = runIntegrityCheck(stagedPath); + const isValid = await verifyDatabaseIntegrityAsync(stagedPath); if (!isValid) { removeFileIfExists(stagedPath); return res diff --git a/backend/src/workers/db-verify.js b/backend/src/workers/db-verify.js new file mode 100644 index 0000000..9fa35c2 --- /dev/null +++ b/backend/src/workers/db-verify.js @@ -0,0 +1,18 @@ +const { parentPort, workerData } = require('worker_threads'); +const Database = require('better-sqlite3'); + +if (!parentPort) throw new Error("Must be run in a worker thread"); + +try { + const { filePath } = workerData; + const db = new Database(filePath, { readonly: true, fileMustExist: true }); + + // This is the CPU-heavy operation + const result = db.prepare("PRAGMA integrity_check;").get(); + + db.close(); + parentPort.postMessage(result.integrity_check === "ok"); +} catch (error) { + // Any error means invalid or corrupt DB + parentPort.postMessage(false); +} \ No newline at end of file