import { serverOrigin } from "@/lib/request-origin"; import { json, readBody, runnerFromRequest } from "@/lib/runner-api"; import { touchRunner } from "@/lib/runners"; import { claimJob } from "@/lib/runs"; /** How long a claim waits for a job to turn up before answering "nothing yet". */ const MAX_WAIT_MS = 25_000; const sleep = (ms: number, signal: AbortSignal) => new Promise((resolve) => { const timer = setTimeout(resolve, ms); signal.addEventListener("abort", () => (clearTimeout(timer), resolve()), { once: true }); }); /** A runner asking for work: answers with a job as soon as one it can run is queued (long polling). */ export async function POST(request: Request) { const info = await readBody(request); const origin = await serverOrigin(); const deadline = Date.now() + MAX_WAIT_MS; for (;;) { // Checked every round: the runner's labels may be edited, or the runner removed, while it waits. const runner = await runnerFromRequest(request); if (runner instanceof Response) return runner; await touchRunner(runner.id, info); if (request.signal.aborted) return new Response(null, { status: 204 }); const job = await claimJob(runner, origin); if (job) return json({ job }); if (Date.now() >= deadline) return new Response(null, { status: 204 }); await sleep(1000, request.signal); } }