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<boolean> {
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<void> {
await releaseUsage(jobHoldId(jobId), ownerId);
}