fix: 加固 Outbox 租约与 Worker 生命周期
This commit is contained in:
+83
-15
@@ -1,23 +1,91 @@
|
||||
import { pathToFileURL } from "node:url";
|
||||
import { PrismaClient } from "@prisma/client";
|
||||
import { ModerationWorker } from "./moderation-worker.js";
|
||||
|
||||
const prisma = new PrismaClient();
|
||||
type SignalSource = {
|
||||
once(signal: "SIGTERM" | "SIGINT", listener: () => void): unknown;
|
||||
removeListener(signal: "SIGTERM" | "SIGINT", listener: () => void): unknown;
|
||||
};
|
||||
|
||||
async function main() {
|
||||
await prisma.$connect();
|
||||
const worker = new ModerationWorker(prisma);
|
||||
if (process.argv.includes("--once")) {
|
||||
await worker.runOnce();
|
||||
return;
|
||||
type RunWorkerOptions = {
|
||||
worker: { runOnce(): Promise<boolean> };
|
||||
connect: () => Promise<unknown>;
|
||||
disconnect: () => Promise<unknown>;
|
||||
once: boolean;
|
||||
sleep?: () => Promise<unknown>;
|
||||
signals?: SignalSource;
|
||||
};
|
||||
|
||||
export async function runWorker(options: RunWorkerOptions) {
|
||||
const signals = options.signals ?? process;
|
||||
const sleep =
|
||||
options.sleep ??
|
||||
(() => new Promise((resolve) => setTimeout(resolve, 1000)));
|
||||
let stopping = false;
|
||||
const stop = () => {
|
||||
stopping = true;
|
||||
};
|
||||
if (!options.once) {
|
||||
signals.once("SIGTERM", stop);
|
||||
signals.once("SIGINT", stop);
|
||||
}
|
||||
for (;;) {
|
||||
const handled = await worker.runOnce();
|
||||
if (!handled) await new Promise((resolve) => setTimeout(resolve, 1000));
|
||||
try {
|
||||
await options.connect();
|
||||
do {
|
||||
const handled = await options.worker.runOnce();
|
||||
if (options.once || stopping) break;
|
||||
if (!handled) await sleep();
|
||||
} while (!stopping);
|
||||
} finally {
|
||||
if (!options.once) {
|
||||
signals.removeListener("SIGTERM", stop);
|
||||
signals.removeListener("SIGINT", stop);
|
||||
}
|
||||
await options.disconnect();
|
||||
}
|
||||
}
|
||||
|
||||
main()
|
||||
.catch(() => {
|
||||
process.exitCode = 1;
|
||||
})
|
||||
.finally(() => prisma.$disconnect());
|
||||
export async function main() {
|
||||
const prisma = new PrismaClient();
|
||||
const worker = new ModerationWorker(prisma);
|
||||
await runWorker({
|
||||
worker,
|
||||
connect: () => prisma.$connect(),
|
||||
disconnect: () => prisma.$disconnect(),
|
||||
once: process.argv.includes("--once"),
|
||||
});
|
||||
}
|
||||
|
||||
export function logTopLevelError(
|
||||
error: unknown,
|
||||
logger: Pick<Console, "error"> = console,
|
||||
) {
|
||||
logger.error(
|
||||
JSON.stringify({
|
||||
scope: "moderation-worker",
|
||||
errorClass:
|
||||
error instanceof Error && error.name ? error.name : "UnknownError",
|
||||
}),
|
||||
);
|
||||
}
|
||||
|
||||
type TopLevelErrorOptions = {
|
||||
logger?: Pick<Console, "error">;
|
||||
processState?: { exitCode?: number };
|
||||
};
|
||||
|
||||
export function handleTopLevelError(
|
||||
error: unknown,
|
||||
options: TopLevelErrorOptions = {},
|
||||
) {
|
||||
logTopLevelError(error, options.logger);
|
||||
(options.processState ?? process).exitCode = 1;
|
||||
}
|
||||
|
||||
const isEntrypoint =
|
||||
process.argv[1] !== undefined &&
|
||||
import.meta.url === pathToFileURL(process.argv[1]).href;
|
||||
|
||||
if (isEntrypoint) {
|
||||
main().catch(handleTopLevelError);
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user