mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-07-26 09:52:11 +03:00
This commit is contained in:
committed by
GitHub
parent
7552f50b91
commit
da2d071d78
1
changelog.d/fixes/7773-termux-sqlite-boot.md
Normal file
1
changelog.d/fixes/7773-termux-sqlite-boot.md
Normal file
@@ -0,0 +1 @@
|
||||
- fix(db): log the fatal boot-time SQLite driver-cascade failure before it propagates instead of swallowing it silently, so `app.log` captures the real cause on Termux/Android (#7773)
|
||||
@@ -82,6 +82,17 @@ export async function ensureDbReadyForBoot(
|
||||
} catch (err: unknown) {
|
||||
const normalized = normalizeBootError(err);
|
||||
if (!TRANSIENT_DB_CLOSED_RE.test(normalized.message)) {
|
||||
// Fatal, non-transient boot-time DB init failure (e.g. the entire
|
||||
// better-sqlite3 -> node:sqlite -> sql.js driver cascade failed, as on
|
||||
// Termux/Android when no SQLite driver is usable). This runs BEFORE
|
||||
// initConsoleInterceptor() is wired up, so this is the only chance to
|
||||
// get the real root cause into stdout/app.log — without it, the
|
||||
// process keeps its HTTP listener up while every DB-touching route
|
||||
// 500s forever with a permanently empty log (#7773).
|
||||
console.error(
|
||||
"[STARTUP] Fatal: Database driver initialization failed:",
|
||||
normalized.message
|
||||
);
|
||||
throw normalized;
|
||||
}
|
||||
console.warn(
|
||||
@@ -91,7 +102,12 @@ export async function ensureDbReadyForBoot(
|
||||
try {
|
||||
await ensureDbInitialized();
|
||||
} catch (retryErr: unknown) {
|
||||
throw normalizeBootError(retryErr);
|
||||
const normalizedRetryErr = normalizeBootError(retryErr);
|
||||
console.error(
|
||||
"[STARTUP] Fatal: Database driver initialization failed after retry:",
|
||||
normalizedRetryErr.message
|
||||
);
|
||||
throw normalizedRetryErr;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
115
tests/unit/instrumentation-node-boot-db-failure.test.ts
Normal file
115
tests/unit/instrumentation-node-boot-db-failure.test.ts
Normal file
@@ -0,0 +1,115 @@
|
||||
import { test } from "node:test";
|
||||
import assert from "node:assert/strict";
|
||||
import { ensureDbReadyForBoot } from "../../src/instrumentation-node";
|
||||
|
||||
// Regression guard for #7773: on Termux/Android, when the whole SQLite driver
|
||||
// cascade (better-sqlite3 -> node:sqlite -> sql.js) fails at boot,
|
||||
// ensureDbReadyForBoot() used to re-throw the fatal error WITHOUT ever
|
||||
// logging it, and this happens before initConsoleInterceptor() runs — so the
|
||||
// one message that would explain the crash never reached app.log. The server
|
||||
// kept listening (hence "OmniRoute is running") but every DB-touching route
|
||||
// 500s forever with a permanently empty log.
|
||||
|
||||
function captureConsole(): { captured: string[]; restore: () => void } {
|
||||
const originalError = console.error;
|
||||
const originalWarn = console.warn;
|
||||
const originalLog = console.log;
|
||||
const captured: string[] = [];
|
||||
const join = (args: unknown[]): string => args.map((arg) => String(arg)).join(" ");
|
||||
console.error = (...args: unknown[]) => {
|
||||
captured.push(join(args));
|
||||
};
|
||||
console.warn = (...args: unknown[]) => {
|
||||
captured.push(join(args));
|
||||
};
|
||||
console.log = (...args: unknown[]) => {
|
||||
captured.push(join(args));
|
||||
};
|
||||
return {
|
||||
captured,
|
||||
restore: () => {
|
||||
console.error = originalError;
|
||||
console.warn = originalWarn;
|
||||
console.log = originalLog;
|
||||
},
|
||||
};
|
||||
}
|
||||
|
||||
test("issue #7773: a fatal (non-transient) boot-time DB init failure must be logged before it propagates", async () => {
|
||||
const { captured, restore } = captureConsole();
|
||||
|
||||
// Mirrors the real message driverFactory.ts's openSqliteDatabase() throws once
|
||||
// better-sqlite3 + node:sqlite are both unavailable AND sql.js pre-init itself
|
||||
// failed (core.ts:172-176) — the exact shape a Termux install with no working
|
||||
// SQLite driver at all would surface.
|
||||
const fatalMessage =
|
||||
"[DB] Nenhum driver SQLite disponível para '/data/data/com.termux/files/home/.omniroute/storage.sqlite'. " +
|
||||
"Drivers testados: better-sqlite3 (falhou), node:sqlite (indisponível), " +
|
||||
"sql.js (falhou: ENOENT: no such file or directory, open '.../sql-wasm.wasm').";
|
||||
|
||||
const fakeEnsureDbInitialized = async () => {
|
||||
throw new Error(fatalMessage);
|
||||
};
|
||||
|
||||
try {
|
||||
await assert.rejects(
|
||||
() => ensureDbReadyForBoot(fakeEnsureDbInitialized),
|
||||
(err: Error) => err.message === fatalMessage
|
||||
);
|
||||
|
||||
const loggedRootCause = captured.some((line) => line.includes(fatalMessage));
|
||||
assert.equal(
|
||||
loggedRootCause,
|
||||
true,
|
||||
"Expected the fatal boot-time DB init failure to be logged (console.error/warn) " +
|
||||
"before propagating, so app.log captures the real driver-cascade failure reason " +
|
||||
"instead of staying empty (#7773). It was NOT logged."
|
||||
);
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
test("issue #7773: a fatal failure on the retry-after-#6560 path must also be logged before it propagates", async () => {
|
||||
const { captured, restore } = captureConsole();
|
||||
|
||||
const fatalMessage = "[DB] sql.js pre-init failed: WASM asset missing on retry";
|
||||
let calls = 0;
|
||||
const fakeEnsureDbInitialized = async () => {
|
||||
calls += 1;
|
||||
if (calls === 1) {
|
||||
// Triggers the existing #6560 transient-retry branch first.
|
||||
throw "Database closed";
|
||||
}
|
||||
throw new Error(fatalMessage);
|
||||
};
|
||||
|
||||
try {
|
||||
await assert.rejects(
|
||||
() => ensureDbReadyForBoot(fakeEnsureDbInitialized),
|
||||
(err: Error) => err.message === fatalMessage
|
||||
);
|
||||
assert.equal(calls, 2, "must have retried exactly once before the fatal retry failure");
|
||||
|
||||
const loggedRootCause = captured.some((line) => line.includes(fatalMessage));
|
||||
assert.equal(
|
||||
loggedRootCause,
|
||||
true,
|
||||
"Expected the fatal retry-path DB init failure to also be logged before propagating (#7773)."
|
||||
);
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
|
||||
test("ensureDbReadyForBoot does not log anything extra on a clean successful boot", async () => {
|
||||
const { captured, restore } = captureConsole();
|
||||
const fakeEnsureDbInitialized = async () => {};
|
||||
|
||||
try {
|
||||
await assert.doesNotReject(ensureDbReadyForBoot(fakeEnsureDbInitialized));
|
||||
assert.equal(captured.length, 0, "a successful boot must not emit any startup DB log lines");
|
||||
} finally {
|
||||
restore();
|
||||
}
|
||||
});
|
||||
Reference in New Issue
Block a user