mirror of
https://github.com/whyour/qinglong.git
synced 2026-09-22 19:29:13 +08:00
feat(ql3): establish 3.0 incubation baseline
This commit is contained in:
@@ -0,0 +1,138 @@
|
||||
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);
|
||||
}
|
||||
Reference in New Issue
Block a user