mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-09 04:50:59 +00:00
The bundled Skia x64 build (document thumbnails, scanned-PDF rasterization) uses AVX2 instructions; on an x86-64 host without them — Celeron/Atom-class NAS CPUs, pre-2013 Xeons, VMs masking CPU flags — the first canvas call is an uncatchable SIGILL that kills the process and crash-loops the container (live incident: a Synology DS1520+, exit 132 ~18 s after every boot, reported with kernel traps and the correct root cause in hand). The renderer is now gated on a one-time CPU-feature check before the module ever loads: linux/x64 requires the avx2 cpuinfo flag; arm64 and non-Linux are unaffected; unreadable cpuinfo fails open. Behind a closed gate, thumbnail generation and PDF rasterization take their existing fail-soft paths (vault shows type icons; PDFs read as text) and the boot-time thumbnail backfill skips enqueueing. NATIVE_CANVAS=off|on overrides detection, documented in the env example and whitelisted in compose.
80 lines
2.9 KiB
TypeScript
80 lines
2.9 KiB
TypeScript
/**
|
|
* v1.28.27 — CPU-feature gate for the native canvas renderer.
|
|
*
|
|
* The bundled `@napi-rs/canvas` (Skia) x64 builds use AVX2 instructions. On an
|
|
* x86-64 host WITHOUT AVX2 — Celeron/Atom-class NAS CPUs (Goldmont Plus and
|
|
* older) are the common self-hosting case — the first canvas call dies with
|
|
* SIGILL (`trap invalid opcode … in skia.linux-x64-musl.node`), which kills the
|
|
* whole Node process and puts the container into an infinite restart loop
|
|
* (live incident: a Synology DS1520+ self-host, exit code 132 ~18 s after
|
|
* every boot once the thumbnail backfill touched Skia).
|
|
*
|
|
* A SIGILL cannot be caught in-process, so the only safe posture is to never
|
|
* load the module on an unsupported CPU. Every canvas consumer (document
|
|
* thumbnails, scanned-PDF rasterization) already has a fail-soft path — with
|
|
* the gate closed those features degrade cleanly (no thumbnails, PDFs read as
|
|
* text/native only) while the rest of the app runs normally.
|
|
*
|
|
* Detection: Linux x64 reads `/proc/cpuinfo` once and requires the `avx2`
|
|
* flag. Non-x64 (the arm64 image) and non-Linux (dev machines) are always
|
|
* supported — AVX is an x86 concept. The optional `NATIVE_CANVAS` env var
|
|
* overrides: `off` force-disables (e.g. to rule the renderer out while
|
|
* debugging), `on` force-enables (only sensible if detection misfires; on a
|
|
* CPU that truly lacks AVX2 this WILL crash the process). Unset or any other
|
|
* value → auto-detection.
|
|
*/
|
|
import { readFileSync } from "node:fs";
|
|
|
|
import { annotate } from "@/lib/logging/context";
|
|
|
|
let cached: boolean | null = null;
|
|
let announced = false;
|
|
|
|
/** Read the gate once per process; logs a single annotation when closed. */
|
|
export function nativeCanvasSupported(): boolean {
|
|
if (cached === null) {
|
|
cached = detect();
|
|
}
|
|
if (!cached && !announced) {
|
|
announced = true;
|
|
annotate({
|
|
action: { name: "documents.nativeCanvas.unsupported" },
|
|
meta: {
|
|
reason: "cpu_missing_avx2",
|
|
arch: process.arch,
|
|
platform: process.platform,
|
|
},
|
|
});
|
|
}
|
|
return cached;
|
|
}
|
|
|
|
function detect(): boolean {
|
|
const override = process.env.NATIVE_CANVAS?.trim().toLowerCase();
|
|
if (override === "off") return false;
|
|
if (override === "on") return true;
|
|
|
|
// AVX is an x86 instruction-set concept; the arm64 image and non-Linux dev
|
|
// hosts never hit the SIGILL class.
|
|
if (process.platform !== "linux" || process.arch !== "x64") return true;
|
|
|
|
try {
|
|
const cpuinfo = readFileSync("/proc/cpuinfo", "utf8");
|
|
const flagsLine = cpuinfo
|
|
.split("\n")
|
|
.find((line) => line.startsWith("flags"));
|
|
// No flags line = exotic kernel surface; fail OPEN to today's behavior
|
|
// rather than silently disabling a working renderer.
|
|
if (!flagsLine) return true;
|
|
return /\bavx2\b/.test(flagsLine);
|
|
} catch {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
/** Test hook — the gate is process-cached by design. */
|
|
export function __resetNativeCanvasSupportForTests(): void {
|
|
cached = null;
|
|
announced = false;
|
|
}
|