mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-09-21 06:12:17 +03:00
fix(db): skip integrity scans during health polling (#13149)
* fix(db): skip integrity scans during health polling * test(db): update health error fixture for scan-free polling * docs(changelog): add fragment for health poll integrity skip * fix(db): keep the #13149 dashboard skip inside the #13717 managed health check Merge fallout only: runManagedDbHealthCheck moved behind the health coordinator on the release tip, so the per-call skipIntegrityCheck now travels through it. A waived integrity scan is part of the job identity, so it is never replayed from the 60s diagnosis cache to a caller that asked for the full scan. Co-authored-by: cryptiklemur <cryptiklemur@users.noreply.github.com> --------- Co-authored-by: diegosouzapw <8016841+diegosouzapw@users.noreply.github.com> Co-authored-by: cryptiklemur <cryptiklemur@users.noreply.github.com>
This commit is contained in:
@@ -9,7 +9,9 @@ export async function GET(request: Request) {
|
||||
}
|
||||
|
||||
try {
|
||||
return NextResponse.json(await runManagedDbHealthCheck({ autoRepair: false }));
|
||||
return NextResponse.json(
|
||||
await runManagedDbHealthCheck({ autoRepair: false, skipIntegrityCheck: true })
|
||||
);
|
||||
} catch (error) {
|
||||
const message = error instanceof Error ? error.message : String(error);
|
||||
console.error("[API] DB health diagnosis failed:", message);
|
||||
|
||||
@@ -943,9 +943,9 @@ function startDbHealthCheckScheduler(db: SqliteDatabase) {
|
||||
// The scheduler lives in ./walMaintenance (periodic TRUNCATE + busy warn + PASSIVE retry).
|
||||
|
||||
const healthShutdown = new AbortController();
|
||||
const managedHealth = createDbHealthCoordinator(async (autoRepair) => {
|
||||
const managedHealth = createDbHealthCoordinator(async (autoRepair, skipIntegrity) => {
|
||||
const db = getDbInstance();
|
||||
const skipIntegrityCheck = process.env.OMNIROUTE_SKIP_DB_HEALTHCHECK === "1";
|
||||
const skipIntegrityCheck = skipIntegrity || process.env.OMNIROUTE_SKIP_DB_HEALTHCHECK === "1";
|
||||
const backupDir = DB_BACKUPS_DIR || path.join(DATA_DIR, "db_backups");
|
||||
const result =
|
||||
db.driver === "sql.js" || db.name === ":memory:" || !db.name
|
||||
@@ -969,10 +969,10 @@ const managedHealth = createDbHealthCoordinator(async (autoRepair) => {
|
||||
if (result.repairedCount > 0) invalidateDbCache();
|
||||
return result;
|
||||
});
|
||||
|
||||
export function runManagedDbHealthCheck(options?: { autoRepair?: boolean }) {
|
||||
type ManagedHealthCheckOptions = { autoRepair?: boolean; skipIntegrityCheck?: boolean };
|
||||
export function runManagedDbHealthCheck(options?: ManagedHealthCheckOptions) {
|
||||
if (getPagerCorruption()) managedHealth.invalidate();
|
||||
return managedHealth.run(options?.autoRepair === true);
|
||||
return managedHealth.run(options?.autoRepair === true, options?.skipIntegrityCheck === true);
|
||||
}
|
||||
|
||||
export function getDbInstance(): SqliteDatabase {
|
||||
|
||||
@@ -12,14 +12,23 @@ export interface DbHealthJob {
|
||||
pagerCorruption: PagerCorruptionNote | null;
|
||||
}
|
||||
|
||||
/**
|
||||
* `skipIntegrityCheck` (#13149): a caller may waive the integrity scan — the dashboard
|
||||
* poll does. It is part of the job identity, so a waived scan is never replayed from the
|
||||
* cache to a caller that asked for a full one (the reverse direction is safe).
|
||||
*/
|
||||
export function createDbHealthCoordinator(
|
||||
execute: (autoRepair: boolean) => Promise<DbHealthCheckResult>,
|
||||
execute: (autoRepair: boolean, skipIntegrityCheck: boolean) => Promise<DbHealthCheckResult>,
|
||||
options: { now?: () => number; cacheMs?: number } = {}
|
||||
) {
|
||||
const now = options.now ?? Date.now;
|
||||
const cacheMs = options.cacheMs ?? 60_000;
|
||||
let stopping = false;
|
||||
let active: { autoRepair: boolean; promise: Promise<DbHealthCheckResult> } | null = null;
|
||||
let active: {
|
||||
autoRepair: boolean;
|
||||
skipIntegrityCheck: boolean;
|
||||
promise: Promise<DbHealthCheckResult>;
|
||||
} | null = null;
|
||||
let cached: { result: DbHealthCheckResult; expires: number } | null = null;
|
||||
return {
|
||||
get busy(): boolean {
|
||||
@@ -34,10 +43,13 @@ export function createDbHealthCoordinator(
|
||||
cancel();
|
||||
await active?.promise.catch(() => {});
|
||||
},
|
||||
run(autoRepair: boolean): Promise<DbHealthCheckResult> {
|
||||
run(autoRepair: boolean, skipIntegrityCheck = false): Promise<DbHealthCheckResult> {
|
||||
if (stopping) return Promise.reject(new Error("Database health checks are stopping"));
|
||||
if (active) {
|
||||
return active.autoRepair === autoRepair
|
||||
// A run that DID scan integrity satisfies a caller that was willing to skip it,
|
||||
// never the reverse (#13149) — so only widen, never narrow, the in-flight job.
|
||||
return active.autoRepair === autoRepair &&
|
||||
(active.skipIntegrityCheck === skipIntegrityCheck || !active.skipIntegrityCheck)
|
||||
? active.promise
|
||||
: Promise.reject(new Error("Database health check already in progress"));
|
||||
}
|
||||
@@ -49,9 +61,11 @@ export function createDbHealthCoordinator(
|
||||
resolve = yes;
|
||||
reject = no;
|
||||
});
|
||||
active = { autoRepair, promise };
|
||||
active = { autoRepair, skipIntegrityCheck, promise };
|
||||
const succeed = (result: DbHealthCheckResult) => {
|
||||
if (!autoRepair) cached = { result, expires: now() + cacheMs };
|
||||
// Only a full (integrity-scanning) diagnosis may be replayed from the cache:
|
||||
// caching a skipped scan would silently downgrade a later full request (#13149).
|
||||
if (!autoRepair && !skipIntegrityCheck) cached = { result, expires: now() + cacheMs };
|
||||
active = null;
|
||||
resolve(result);
|
||||
};
|
||||
@@ -60,7 +74,7 @@ export function createDbHealthCoordinator(
|
||||
reject(error);
|
||||
};
|
||||
try {
|
||||
execute(autoRepair).then(succeed, fail);
|
||||
execute(autoRepair, skipIntegrityCheck).then(succeed, fail);
|
||||
} catch (error) {
|
||||
fail(error);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user