Add a switch to turn job dispatch off, and use it in the dev stack
The scheduler sends a queued job to any idle claimed printer in any farm. The dashboard dev stack is wired to the real printers, so testing the job upload UI there would have started a real print on the first idle one. DISPATCH_ENABLED=false (exactly that string) disables the timer and the nudge from POST /jobs; any other value that looks like an attempt to disable it is warned about, because a typo that silently leaves it on is the dangerous direction. scripts/e2e-camera.sh now always runs the backend with it off.
This commit is contained in:
@@ -53,3 +53,8 @@ SUPABASE_URL=
|
||||
# refuses to start without it, because anything that knows this secret can
|
||||
# connect as any farm's gateway. Generate one with: openssl rand -hex 32
|
||||
EDGE_JWT_SECRET=
|
||||
|
||||
# The scheduler sends queued jobs to any idle, claimed printer in any farm.
|
||||
# Set to "false" (exactly) on a stack wired to real printers that should not
|
||||
# start prints - scripts/e2e-camera.sh does this for you. Unset = on.
|
||||
# DISPATCH_ENABLED=false
|
||||
|
||||
@@ -245,7 +245,11 @@ if [ "$AUTH" = "dev" ]; then
|
||||
else
|
||||
echo "==> starting the backend on :$PORT, verifying tokens against the real Supabase JWKS"
|
||||
fi
|
||||
bun run src/index.ts > "$WORK/backend.log" 2>&1 &
|
||||
# The scheduler would send any queued job to any idle printer in the fleet - and
|
||||
# this stack is wired to the real ones. Off, always: a job queued while working
|
||||
# on the dashboard must never become a real print. (Dispatch is exercised
|
||||
# against fakes by the other e2e scripts.)
|
||||
DISPATCH_ENABLED=false bun run src/index.ts > "$WORK/backend.log" 2>&1 &
|
||||
BACKEND_PID=$!
|
||||
for _ in $(seq 1 60); do curl -sf "$BASE/health" >/dev/null 2>&1 && break; sleep 0.5; done
|
||||
curl -sf "$BASE/health" >/dev/null || fail "backend did not become healthy"
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
/**
|
||||
* Whether the scheduler may put jobs on printers. On unless
|
||||
* `DISPATCH_ENABLED=false`.
|
||||
*
|
||||
* Exists because dispatch is global: it sends a queued job to *any* idle,
|
||||
* claimed printer in *any* farm, so a development stack wired to real
|
||||
* printers would start a real print the moment anyone queued a job. The dev
|
||||
* stack (`SERVE=1 scripts/e2e-camera.sh`) turns it off; production leaves it
|
||||
* unset.
|
||||
*
|
||||
* Only the exact string `false` disables it. A typo ("0", "off", "False")
|
||||
* leaving the scheduler running is the safe-for-the-product failure, but a
|
||||
* loud one would be better, so anything else that looks like an attempt to
|
||||
* disable it is reported by `dispatchSwitchWarning`.
|
||||
*/
|
||||
export function dispatchEnabled(env: Record<string, string | undefined> = process.env): boolean {
|
||||
return env.DISPATCH_ENABLED !== "false";
|
||||
}
|
||||
|
||||
/** A message when `DISPATCH_ENABLED` is set to something that does not disable it. */
|
||||
export function dispatchSwitchWarning(env: Record<string, string | undefined> = process.env): string | undefined {
|
||||
const value = env.DISPATCH_ENABLED;
|
||||
if (value === undefined || value === "" || value === "true" || value === "false") return undefined;
|
||||
return `DISPATCH_ENABLED=${JSON.stringify(value)} does not disable the scheduler (only "false" does); jobs WILL be dispatched`;
|
||||
}
|
||||
@@ -4,6 +4,7 @@ import { printers, printJobs } from "../db/schema";
|
||||
import { getDownloadUrl, headObject } from "../s3";
|
||||
import { isGatewayOnline, sendPrinterCommand } from "../routes/ws/edge";
|
||||
import { nextJobStatus } from "./job_status";
|
||||
import { dispatchEnabled, dispatchSwitchWarning } from "./enabled";
|
||||
|
||||
/**
|
||||
* The job scheduler: picks up `queued` jobs and puts them on idle printers.
|
||||
@@ -152,6 +153,8 @@ async function releaseJob(jobId: string, outcome: "requeue" | "fail", reason: st
|
||||
export { nextJobStatus } from "./job_status";
|
||||
|
||||
export async function dispatchTick(): Promise<number> {
|
||||
// Also covers the nudge from `POST /jobs`, which calls this directly.
|
||||
if (!dispatchEnabled()) return 0;
|
||||
await reapStalledUploads();
|
||||
|
||||
let dispatched = 0;
|
||||
@@ -286,6 +289,12 @@ let timer: ReturnType<typeof setInterval> | undefined;
|
||||
/** Starts the scheduler. Idempotent, so a second call does not double-tick. */
|
||||
export function startDispatcher() {
|
||||
if (timer) return;
|
||||
const warning = dispatchSwitchWarning();
|
||||
if (warning) console.warn(`[dispatch] ${warning}`);
|
||||
if (!dispatchEnabled()) {
|
||||
console.log("[dispatch] DISABLED (DISPATCH_ENABLED=false): queued jobs will not be sent to printers");
|
||||
return;
|
||||
}
|
||||
timer = setInterval(() => {
|
||||
dispatchTick().catch((err) => console.error("[dispatch] tick failed", err));
|
||||
}, TICK_MS);
|
||||
|
||||
@@ -70,3 +70,22 @@ describe("nextJobStatus", () => {
|
||||
expect(completable).toEqual(["printing", "paused"]);
|
||||
});
|
||||
});
|
||||
|
||||
describe("the dispatch switch", () => {
|
||||
test("is on by default and off only for the exact string false", async () => {
|
||||
const { dispatchEnabled } = await import("../src/dispatch/enabled");
|
||||
expect(dispatchEnabled({})).toBe(true);
|
||||
expect(dispatchEnabled({ DISPATCH_ENABLED: "true" })).toBe(true);
|
||||
expect(dispatchEnabled({ DISPATCH_ENABLED: "false" })).toBe(false);
|
||||
});
|
||||
|
||||
test("a value that looks like an attempt to disable it but does not is reported", async () => {
|
||||
const { dispatchEnabled, dispatchSwitchWarning } = await import("../src/dispatch/enabled");
|
||||
for (const typo of ["0", "off", "False", "no"]) {
|
||||
expect(dispatchEnabled({ DISPATCH_ENABLED: typo })).toBe(true);
|
||||
expect(dispatchSwitchWarning({ DISPATCH_ENABLED: typo })).toContain("WILL be dispatched");
|
||||
}
|
||||
expect(dispatchSwitchWarning({})).toBeUndefined();
|
||||
expect(dispatchSwitchWarning({ DISPATCH_ENABLED: "false" })).toBeUndefined();
|
||||
});
|
||||
});
|
||||
|
||||
Reference in New Issue
Block a user