mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-21 01:32:44 +08:00
139 lines
3.9 KiB
TypeScript
139 lines
3.9 KiB
TypeScript
export const RUN_RETRY_SAFETIES = [
|
|
'unknown',
|
|
'idempotent',
|
|
'deduplicated',
|
|
] as const;
|
|
|
|
export type RunRetrySafety = (typeof RUN_RETRY_SAFETIES)[number];
|
|
|
|
export const MAX_RUN_ATTEMPTS = 16;
|
|
export const MAX_RUN_RETRY_BACKOFF_MS = 24 * 60 * 60 * 1000;
|
|
|
|
export interface RunRetryPolicyDefinition {
|
|
maxAttempts: number;
|
|
retryOnLost: boolean;
|
|
safety: RunRetrySafety;
|
|
backoffBaseMs: number;
|
|
backoffMaxMs: number;
|
|
}
|
|
|
|
export interface RunRetryPolicyRecord extends RunRetryPolicyDefinition {
|
|
runId: string;
|
|
nextAttemptAtMs?: number;
|
|
version: number;
|
|
createdAtMs: number;
|
|
updatedAtMs: number;
|
|
}
|
|
|
|
export const NO_AUTOMATIC_RUN_RETRY: Readonly<RunRetryPolicyDefinition> = {
|
|
maxAttempts: 1,
|
|
retryOnLost: false,
|
|
safety: 'unknown',
|
|
backoffBaseMs: 0,
|
|
backoffMaxMs: 0,
|
|
};
|
|
|
|
export class InvalidRunRetryPolicyError extends TypeError {
|
|
readonly code = 'INVALID_RUN_RETRY_POLICY';
|
|
|
|
constructor(message: string) {
|
|
super(message);
|
|
this.name = 'InvalidRunRetryPolicyError';
|
|
}
|
|
}
|
|
|
|
function assertNonNegativeTime(name: string, value: number): void {
|
|
if (!Number.isSafeInteger(value) || value < 0) {
|
|
throw new InvalidRunRetryPolicyError(
|
|
`${name} must be a non-negative safe integer`,
|
|
);
|
|
}
|
|
}
|
|
|
|
export function assertRunRetryPolicyDefinition(
|
|
policy: RunRetryPolicyDefinition,
|
|
): void {
|
|
if (
|
|
!Number.isSafeInteger(policy.maxAttempts) ||
|
|
policy.maxAttempts < 1 ||
|
|
policy.maxAttempts > MAX_RUN_ATTEMPTS
|
|
) {
|
|
throw new InvalidRunRetryPolicyError(
|
|
`maxAttempts must be between 1 and ${MAX_RUN_ATTEMPTS}`,
|
|
);
|
|
}
|
|
if (typeof policy.retryOnLost !== 'boolean') {
|
|
throw new InvalidRunRetryPolicyError('retryOnLost must be boolean');
|
|
}
|
|
if (!RUN_RETRY_SAFETIES.includes(policy.safety)) {
|
|
throw new InvalidRunRetryPolicyError('safety is not supported');
|
|
}
|
|
assertNonNegativeTime('backoffBaseMs', policy.backoffBaseMs);
|
|
assertNonNegativeTime('backoffMaxMs', policy.backoffMaxMs);
|
|
if (
|
|
policy.backoffBaseMs > MAX_RUN_RETRY_BACKOFF_MS ||
|
|
policy.backoffMaxMs > MAX_RUN_RETRY_BACKOFF_MS
|
|
) {
|
|
throw new InvalidRunRetryPolicyError(
|
|
`retry backoff cannot exceed ${MAX_RUN_RETRY_BACKOFF_MS}ms`,
|
|
);
|
|
}
|
|
if (policy.backoffMaxMs < policy.backoffBaseMs) {
|
|
throw new InvalidRunRetryPolicyError(
|
|
'backoffMaxMs cannot be smaller than backoffBaseMs',
|
|
);
|
|
}
|
|
}
|
|
|
|
export function assertAdmittedRunRetryPolicy(
|
|
policy: RunRetryPolicyDefinition,
|
|
): void {
|
|
assertRunRetryPolicyDefinition(policy);
|
|
if (
|
|
policy.retryOnLost &&
|
|
policy.maxAttempts > 1 &&
|
|
policy.safety === 'unknown'
|
|
) {
|
|
throw new InvalidRunRetryPolicyError(
|
|
'automatic lost retry requires idempotent or deduplicated safety',
|
|
);
|
|
}
|
|
}
|
|
|
|
export function assertRunRetryPolicyRecord(policy: RunRetryPolicyRecord): void {
|
|
assertRunRetryPolicyDefinition(policy);
|
|
if (!policy.runId) {
|
|
throw new InvalidRunRetryPolicyError('runId is required');
|
|
}
|
|
if (!Number.isSafeInteger(policy.version) || policy.version < 0) {
|
|
throw new InvalidRunRetryPolicyError(
|
|
'version must be a non-negative safe integer',
|
|
);
|
|
}
|
|
assertNonNegativeTime('createdAtMs', policy.createdAtMs);
|
|
assertNonNegativeTime('updatedAtMs', policy.updatedAtMs);
|
|
if (policy.updatedAtMs < policy.createdAtMs) {
|
|
throw new InvalidRunRetryPolicyError(
|
|
'updatedAtMs cannot be earlier than createdAtMs',
|
|
);
|
|
}
|
|
if (policy.nextAttemptAtMs !== undefined) {
|
|
assertNonNegativeTime('nextAttemptAtMs', policy.nextAttemptAtMs);
|
|
}
|
|
}
|
|
|
|
export function runRetryDelayMs(
|
|
policy: RunRetryPolicyDefinition,
|
|
lostAttempt: number,
|
|
): number {
|
|
assertRunRetryPolicyDefinition(policy);
|
|
if (!Number.isSafeInteger(lostAttempt) || lostAttempt < 1) {
|
|
throw new InvalidRunRetryPolicyError(
|
|
'lostAttempt must be a positive safe integer',
|
|
);
|
|
}
|
|
if (policy.backoffBaseMs === 0) return 0;
|
|
const exponent = Math.min(lostAttempt - 1, MAX_RUN_ATTEMPTS - 1);
|
|
return Math.min(policy.backoffMaxMs, policy.backoffBaseMs * 2 ** exponent);
|
|
}
|