111 lines
4.2 KiB
TypeScript
111 lines
4.2 KiB
TypeScript
import { Worker, type Job } from "bullmq";
|
|
import { spawn, type ChildProcess } from "child_process";
|
|
import { existsSync } from "fs";
|
|
import { createBullMQConnection } from "./redis.js";
|
|
import { handleBuildValhalla } from "./jobs/build-valhalla.js";
|
|
import { handleDownloadGtfsDe } from "./jobs/download-gtfs-de.js";
|
|
|
|
const VALHALLA_CONFIG = process.env.VALHALLA_CONFIG ?? "/data/valhalla/valhalla.json";
|
|
const VALHALLA_QUEUE_NAME = process.env.VALHALLA_QUEUE_NAME ?? "valhalla";
|
|
|
|
console.log(`[valhalla-worker] Starting Transportationer Valhalla worker (queue=${VALHALLA_QUEUE_NAME})…`);
|
|
|
|
// ─── Valhalla service process manager ─────────────────────────────────────────
|
|
// The valhalla_service HTTP server runs as a child process alongside this
|
|
// BullMQ worker. When a build-valhalla job arrives, we stop the server, rebuild
|
|
// the routing tiles (using the Valhalla tools installed in this container),
|
|
// then restart the server.
|
|
|
|
let valhallaProc: ChildProcess | null = null;
|
|
|
|
function startValhallaService(): void {
|
|
if (!existsSync(VALHALLA_CONFIG)) {
|
|
console.log("[valhalla-worker] No config yet — will start after first tile build");
|
|
return;
|
|
}
|
|
console.log("[valhalla-worker] Starting valhalla_service…");
|
|
// valhalla_service <config_file> [concurrency] — positional arg, not -c flag
|
|
valhallaProc = spawn("valhalla_service", [VALHALLA_CONFIG], {
|
|
stdio: "inherit",
|
|
});
|
|
valhallaProc.on("exit", (code, signal) => {
|
|
console.log(`[valhalla-worker] valhalla_service exited (code=${code}, signal=${signal})`);
|
|
valhallaProc = null;
|
|
});
|
|
}
|
|
|
|
function stopValhallaService(): Promise<void> {
|
|
return new Promise((resolve) => {
|
|
if (!valhallaProc) { resolve(); return; }
|
|
const proc = valhallaProc;
|
|
proc.once("exit", () => resolve());
|
|
proc.kill("SIGTERM");
|
|
// Force kill after 10 s if it doesn't exit cleanly
|
|
setTimeout(() => {
|
|
if (valhallaProc === proc) proc.kill("SIGKILL");
|
|
}, 10_000);
|
|
});
|
|
}
|
|
|
|
// ─── BullMQ worker ────────────────────────────────────────────────────────────
|
|
|
|
const worker = new Worker(
|
|
VALHALLA_QUEUE_NAME,
|
|
async (job: Job) => {
|
|
console.log(`[valhalla-worker] Processing job ${job.id} type=${job.data.type} city=${job.data.citySlug ?? "(rebuild)"}`);
|
|
|
|
// Valhalla keeps serving old tiles while the new tiles are being built.
|
|
// restartService is called from inside handleBuildValhalla only after the
|
|
// tile build completes — the service is only down for the few seconds it
|
|
// takes to restart, and compute-routing jobs retry transparently across that
|
|
// window via fetchMatrix's built-in retry logic.
|
|
async function restartService(): Promise<void> {
|
|
await stopValhallaService();
|
|
startValhallaService();
|
|
}
|
|
|
|
if (job.data.type === "download-gtfs-de") {
|
|
await handleDownloadGtfsDe(job as any);
|
|
return;
|
|
}
|
|
await handleBuildValhalla(job as any, restartService);
|
|
},
|
|
{
|
|
connection: createBullMQConnection(),
|
|
concurrency: 1,
|
|
lockDuration: 1_800_000, // 30 min — large-region tile builds can be very slow
|
|
lockRenewTime: 60_000,
|
|
},
|
|
);
|
|
|
|
worker.on("completed", (job) => {
|
|
console.log(`[valhalla-worker] ✓ Job ${job.id} (${job.data.type}) completed`);
|
|
});
|
|
|
|
worker.on("failed", (job, err) => {
|
|
console.error(`[valhalla-worker] ✗ Job ${job?.id} (${job?.data?.type}) failed:`, err.message);
|
|
});
|
|
|
|
worker.on("active", (job) => {
|
|
const city = job.data.citySlug ?? job.data.removeSlugs?.join(",") ?? "rebuild";
|
|
console.log(`[valhalla-worker] → Job ${job.id} (${job.data.type}) started city=${city}`);
|
|
});
|
|
|
|
worker.on("error", (err) => {
|
|
console.error("[valhalla-worker] Worker error:", err.message);
|
|
});
|
|
|
|
const shutdown = async () => {
|
|
console.log("[valhalla-worker] Shutting down gracefully…");
|
|
await worker.close();
|
|
await stopValhallaService();
|
|
process.exit(0);
|
|
};
|
|
|
|
process.on("SIGTERM", shutdown);
|
|
process.on("SIGINT", shutdown);
|
|
|
|
// Start serving if tiles already exist from a previous run
|
|
startValhallaService();
|
|
|
|
console.log(`[valhalla-worker] Ready — waiting for jobs on '${VALHALLA_QUEUE_NAME}' queue`);
|