import { QuotaExceededError, releaseUsage, reserveUsage } from "../metering/quota"; import { runnerMeter } from "../metering/ci"; const MINUTE_MS = 60 * 1000; const HOLD_SLACK_MS = 60 * MINUTE_MS; export function jobHoldId(jobId: string): string { return `ci-hold:${jobId}`; } /** * Holds the job's full timeout on its own runner-size meter before it may start, so large/xlarge jobs hit * their hard caps and the spending limit at their real price, and concurrent dispatches can't overshoot. * False when the plan has no room for it. */ export async function holdJobMinutes(input: { jobId: string; ownerId: string; runsOn: string[]; timeoutMinutes: number; }): Promise { const timeoutMs = Math.max(1, input.timeoutMinutes) * MINUTE_MS; try { await reserveUsage({ id: jobHoldId(input.jobId), accountId: input.ownerId, meter: runnerMeter(input.runsOn), quantity: timeoutMs / 1000, ttlMs: timeoutMs + HOLD_SLACK_MS, }); return true; } catch (error) { if (error instanceof QuotaExceededError) return false; throw error; } } export async function releaseJobHold(jobId: string, ownerId: string): Promise { await releaseUsage(jobHoldId(jobId), ownerId); }