mirror of
https://github.com/snapotter-hq/SnapOtter.git
synced 2026-08-03 07:46:42 +02:00
fix(jobs): classify BullMQ stall (UnrecoverableError) as operational (#610)
BullMQ raises UnrecoverableError when a job loses its lock (a stall), e.g. a heavy upscale under memory pressure. We never throw it ourselves, so classifyError now treats it as operational (one warning per hour) instead of a bug. ReplyError stays a bug.
This commit is contained in:
@@ -64,6 +64,12 @@ export function classifyError(err: unknown, source?: ReportContext["source"]): E
|
|||||||
// a worker-side ZodError is schema drift (our bug); only expected on http.
|
// a worker-side ZodError is schema drift (our bug); only expected on http.
|
||||||
if (e?.name === "ZodError") return "expected";
|
if (e?.name === "ZodError") return "expected";
|
||||||
}
|
}
|
||||||
|
// BullMQ raises UnrecoverableError from its own worker run loop when a job
|
||||||
|
// loses its lock (a stall), e.g. a heavy upscale under CPU/memory pressure.
|
||||||
|
// We never throw it ourselves, so it always means the instance could not keep
|
||||||
|
// the job's lock alive: an environmental strain, not a defect in our code.
|
||||||
|
// Operational (one warning/hour) instead of bug spam (NODE-27).
|
||||||
|
if (e?.name === "UnrecoverableError") return "operational";
|
||||||
if (isSafeMessageError(err)) return err.kind === "bug" ? "bug" : "operational";
|
if (isSafeMessageError(err)) return err.kind === "bug" ? "bug" : "operational";
|
||||||
if (connectivityClass(err)) return "operational";
|
if (connectivityClass(err)) return "operational";
|
||||||
if (isEnvironmentalDbError(err)) return "operational";
|
if (isEnvironmentalDbError(err)) return "operational";
|
||||||
|
|||||||
@@ -99,6 +99,18 @@ describe("classifyError", () => {
|
|||||||
expect(classifyError(e, "http")).toBe("expected");
|
expect(classifyError(e, "http")).toBe("expected");
|
||||||
expect(classifyError(e)).toBe("expected");
|
expect(classifyError(e)).toBe("expected");
|
||||||
});
|
});
|
||||||
|
it("operational: a BullMQ UnrecoverableError (stalled/lock-lost job) is a strained instance, not our bug", () => {
|
||||||
|
// BullMQ raises UnrecoverableError from its own worker loop when a job loses
|
||||||
|
// its lock (a stall), e.g. a heavy `upscale` under CPU/memory pressure. We
|
||||||
|
// never throw it ourselves, so it always means the instance could not keep
|
||||||
|
// the job's lock alive -- environmental, worth one warning/hour not bug spam
|
||||||
|
// (NODE-27). A ReplyError (a real Redis command failure) stays a bug.
|
||||||
|
const stalled = Object.assign(new Error("Missing lock for job 42. moveToFinished"), {
|
||||||
|
name: "UnrecoverableError",
|
||||||
|
});
|
||||||
|
expect(classifyError(stalled, "worker")).toBe("operational");
|
||||||
|
expect(classifyError(stalled)).toBe("operational");
|
||||||
|
});
|
||||||
});
|
});
|
||||||
|
|
||||||
describe("throttle", () => {
|
describe("throttle", () => {
|
||||||
|
|||||||
Reference in New Issue
Block a user