mirror of
https://github.com/MBombeck/HealthLog.git
synced 2026-11-13 14:32:03 +00:00
Merge branch 'feat/v147-wolfi' into release/v1.47.0
This commit is contained in:
commit
a503c8855d
12 files changed
+184
-89
No files matched your search
@@ -6,7 +6,7 @@
|
||||
*
|
||||
* The pnpm store held two canvas versions: the one in package.json and an
|
||||
* older one that a second, older pdfjs-dist pulled in (gone since #1277).
|
||||
* The runner stage picked the musl binary with `find | head -1`, got the
|
||||
* The runner stage picked the platform binary with `find | head -1`, got the
|
||||
* old one, and copied it into the new loader's directory, where the loader
|
||||
* looks first. PDF rendering still worked, so nothing looked wrong;
|
||||
* `loadImage` called an `Image.decode` the old binary lacks, and every
|
||||
@@ -43,7 +43,7 @@ describe("canvas binary hoist", () => {
|
||||
);
|
||||
});
|
||||
|
||||
it("hoists the loader and the musl binary of that version only", () => {
|
||||
it("hoists the loader and the glibc binary of that version only", () => {
|
||||
const runner = stage("runner");
|
||||
expect(runner).toContain(
|
||||
"COPY --from=builder /app/.canvas-version /tmp/.canvas-version",
|
||||
@@ -51,12 +51,38 @@ describe("canvas binary hoist", () => {
|
||||
expect(runner).toContain(
|
||||
'CANVAS_DIR="/app/node_modules/.pnpm/@napi-rs+canvas@${CANVAS_VERSION}/',
|
||||
);
|
||||
expect(runner).toContain("canvas-linux-*-musl@${CANVAS_VERSION}/");
|
||||
// The image is Debian: the glibc (`-gnu`) build is the one that runs,
|
||||
// and the musl build, which needs ARMv8.2 on arm64 (#1311), is not.
|
||||
expect(runner).toContain("canvas-linux-*-gnu@${CANVAS_VERSION}/");
|
||||
expect(runner).toContain("-name 'skia.linux-*-gnu.node'");
|
||||
expect(runner).not.toMatch(/-musl/);
|
||||
// A version-blind pick is the defect: any `canvas…@*` glob decides by
|
||||
// readdir order which of the store's copies wins.
|
||||
expect(runner).not.toMatch(/@napi-rs\+canvas[^"'\s]*@\*/);
|
||||
});
|
||||
|
||||
it("installs on Debian and runs on Wolfi, both glibc", () => {
|
||||
const froms = DOCKERFILE.match(/^FROM \S+ AS \S+$/gm) ?? [];
|
||||
expect(froms).toEqual([
|
||||
expect.stringMatching(
|
||||
/^FROM node:22-trixie-slim@sha256:[0-9a-f]{64} AS deps$/,
|
||||
),
|
||||
expect.stringMatching(
|
||||
/^FROM node:22-trixie-slim@sha256:[0-9a-f]{64} AS builder$/,
|
||||
),
|
||||
expect.stringMatching(
|
||||
/^FROM cgr\.dev\/chainguard\/wolfi-base:latest@sha256:[0-9a-f]{64} AS runner$/,
|
||||
),
|
||||
]);
|
||||
// The native packages pnpm picked in the builder must load in the
|
||||
// runner: same module ABI, and a glibc no older than the builder's.
|
||||
expect(stage("builder")).toContain("> /app/.native-abi");
|
||||
expect(stage("runner")).toContain(
|
||||
'[ "$RUN_ABI" = "$BUILD_ABI" ] || { echo "ERROR: runner Node module ABI',
|
||||
);
|
||||
expect(stage("runner")).toContain("is older than the builder's");
|
||||
});
|
||||
|
||||
it("fails the build when the copied binary is another version", () => {
|
||||
const runner = stage("runner");
|
||||
expect(runner).toMatch(
|
||||
|
||||
@@ -79,10 +79,21 @@ function imageCheck(): Check {
|
||||
};
|
||||
}
|
||||
|
||||
function wgetTimeout(command: string): number {
|
||||
const m = /wget[^|]*--timeout=(\d+)/.exec(command);
|
||||
if (!m) throw new Error(`no wget timeout in: ${command}`);
|
||||
return Number(m[1]);
|
||||
/**
|
||||
* The probe is Node's fetch, aborted by `AbortSignal.timeout(ms)`. The
|
||||
* image carries no wget or curl, so a probe that names one would fail every
|
||||
* time and mark a healthy container unhealthy.
|
||||
*/
|
||||
function probeTimeout(command: string): number {
|
||||
expect(command, "the probe needs a binary the image has").not.toMatch(
|
||||
/\b(wget|curl)\b/,
|
||||
);
|
||||
const m =
|
||||
/fetch\('http:\/\/127\.0\.0\.1:3000\/api\/health',\{signal:AbortSignal\.timeout\((\d+)\)\}\)/.exec(
|
||||
command,
|
||||
);
|
||||
if (!m) throw new Error(`no aborting /api/health fetch in: ${command}`);
|
||||
return Number(m[1]) / 1000;
|
||||
}
|
||||
|
||||
describe.each([
|
||||
@@ -96,9 +107,9 @@ describe.each([
|
||||
|
||||
it("ends the request inside Docker's timeout, after the database probe's", () => {
|
||||
const check = read();
|
||||
const wget = wgetTimeout(check.command);
|
||||
expect(wget).toBeGreaterThan(probeTimeoutSeconds);
|
||||
expect(wget).toBeLessThan(check.timeout);
|
||||
const abort = probeTimeout(check.command);
|
||||
expect(abort).toBeGreaterThan(probeTimeoutSeconds);
|
||||
expect(abort).toBeLessThan(check.timeout);
|
||||
});
|
||||
});
|
||||
|
||||
|
||||
@@ -1,13 +1,19 @@
|
||||
/**
|
||||
* v1.28.27 — CPU-feature gate for the native canvas renderer.
|
||||
*
|
||||
* The bundled `@napi-rs/canvas` (Skia) x64 builds use AVX2 instructions. On an
|
||||
* The `@napi-rs/canvas` (Skia) x64 builds carry 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).
|
||||
* older) are the common self-hosting case — the musl build died on the first
|
||||
* canvas call 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).
|
||||
*
|
||||
* The published image runs on glibc (Wolfi) and loads the glibc build. Under an
|
||||
* emulated AVX2-less Atom core that build drew a canvas and rendered
|
||||
* a PDF page to JPEG where the musl build died, but it still carries AVX2 code
|
||||
* and not every path it can take was exercised, so the x64 gate stays closed
|
||||
* without the flag. `NATIVE_CANVAS=on` is the way to try it on such a host.
|
||||
*
|
||||
* 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
|
||||
@@ -15,12 +21,13 @@
|
||||
* the gate closed those features degrade cleanly (no thumbnails, PDFs read as
|
||||
* text/native only) while the rest of the app runs normally.
|
||||
*
|
||||
* The arm64 musl build (the one the published arm64 image runs) is compiled
|
||||
* for ARMv8.2 and later: it uses LSE atomics (`ldaddal`, `casal`) and RCpc
|
||||
* loads (`ldapr`) throughout, with no runtime fallback, and dies with SIGILL
|
||||
* as soon as the module loads on an ARMv8.0 core such as the Raspberry Pi 4's
|
||||
* Cortex-A72. The glibc arm64 build uses neither, so a glibc host is left
|
||||
* alone.
|
||||
* The arm64 musl build is compiled for ARMv8.2 and later: it uses LSE atomics
|
||||
* (`ldaddal`, `casal`) and RCpc loads (`ldapr`) throughout, with no runtime
|
||||
* fallback, and dies with SIGILL as soon as the module loads on an ARMv8.0
|
||||
* core such as the Raspberry Pi 4's Cortex-A72 (#1311). The glibc arm64 build
|
||||
* uses neither and runs there, which is why the published image moved off
|
||||
* musl. The musl branch stays for a host that runs the app on musl itself,
|
||||
* from source on Alpine or from an image built on it.
|
||||
*
|
||||
* Detection reads `/proc/cpuinfo` once on Linux: x64 requires the `avx2`
|
||||
* flag, arm64 under musl requires the `atomics` and `lrcpc` features. Every
|
||||
|
||||
Reference in new issue
Block a user