fix(services): сразу возвращать IP в DNS после первой успешной пробы
quality / commitlint (push) Skipped
CD / update-wiki (push) Successful in 8s
quality / changes (push) Successful in 8s
quality / web (push) Skipped
quality / docker-check (push) Skipped
quality / api (push) Successful in 1m20s
CD / quality (push) Successful in 1m31s
CD / publish (push) Successful in 2m4s

Co-authored-by: Cursor <[email protected]>
This commit is contained in:
Denozordec
2026-08-20 19:51:23 +07:00
co-authored by Cursor
parent f1443f1db5
commit 5cf39880f8
9 changed files with 73 additions and 22 deletions
@@ -290,7 +290,7 @@ export interface RunAllChecksOptions {
target: HealthCheckTarget,
prevState: IpHealthState | null,
nextState: IpHealthState,
) => void;
) => void | Promise<void>;
}
function sleep(ms: number): Promise<void> {
@@ -326,7 +326,7 @@ function logSourceResult(
});
}
function applyAggregatedStatus(
async function applyAggregatedStatus(
db: Db,
target: HealthCheckTarget,
sources: Array<{ provider: HealthCheckProvider; result: ProbeResult }>,
@@ -397,7 +397,7 @@ function applyAggregatedStatus(
});
}
if (prevState !== state) {
options.onStatusChange?.(target, prevState, state);
await options.onStatusChange?.(target, prevState, state);
}
}
@@ -513,7 +513,7 @@ export async function runAllChecks(
for (const source of sources) {
logSourceResult(db, target, source.provider, source.result);
}
applyAggregatedStatus(db, target, sources, options);
await applyAggregatedStatus(db, target, sources, options);
}
}
+4 -4
View File
@@ -1,16 +1,16 @@
import type { LbIpRow } from "./types.js";
import { isHealthy } from "./health.js";
import { isPoolMember } from "./health.js";
export function failoverDesired(rows: LbIpRow[]): string[] {
if (rows.length === 0) return [];
const healthy = rows.filter((r) => isHealthy(r.health));
const pool = healthy.length > 0 ? healthy : rows;
const live = rows.filter((r) => isPoolMember(r.health));
const pool = live.length > 0 ? live : rows;
const sorted = [...pool].sort(
(a, b) => a.priority - b.priority || a.weight - b.weight,
);
const minPriority = sorted[0]!.priority;
const primaries = sorted.filter((r) => r.priority === minPriority);
if (healthy.length > 0) {
if (live.length > 0) {
return primaries.map((r) => r.ip);
}
return [sorted[0]!.ip];
+9
View File
@@ -3,3 +3,12 @@ import type { IpHealthState, NodeHealthState } from "@cfdm/shared";
export function isHealthy(state: IpHealthState | NodeHealthState | string): boolean {
return state === "up" || state === "healthy";
}
export function isDown(state: IpHealthState | NodeHealthState | string): boolean {
return state === "down" || state === "unhealthy";
}
/** A-pool membership: only Down is drained. Recovering (unknown/checking) and Slow return immediately. */
export function isPoolMember(state: IpHealthState | NodeHealthState | string): boolean {
return !isDown(state);
}
+1 -1
View File
@@ -6,7 +6,7 @@ import type { LbIpRow, LbTargetConfig } from "./types.js";
import { weightedDesired } from "./weighted.js";
export type { LbIpRow, LbTargetConfig } from "./types.js";
export { isHealthy } from "./health.js";
export { isDown, isHealthy, isPoolMember } from "./health.js";
export { withBindingLock } from "./binding-lock.js";
export {
canApplyLb,
+3 -3
View File
@@ -1,8 +1,8 @@
import type { LbIpRow } from "./types.js";
import { isHealthy } from "./health.js";
import { isPoolMember } from "./health.js";
export function roundRobinDesired(rows: LbIpRow[]): string[] {
const healthy = rows.filter((r) => isHealthy(r.health));
const pool = healthy.length > 0 ? healthy : rows;
const live = rows.filter((r) => isPoolMember(r.health));
const pool = live.length > 0 ? live : rows;
return pool.map((r) => r.ip);
}
+3 -3
View File
@@ -1,5 +1,5 @@
import type { LbIpRow } from "./types.js";
import { isHealthy } from "./health.js";
import { isPoolMember } from "./health.js";
/** Slot length for time-sliced weighted DNS (one A at a time). */
export const WEIGHTED_SLOT_MS = 60_000;
@@ -9,8 +9,8 @@ export const WEIGHTED_DNS_TTL = 60;
export function weightedDesired(rows: LbIpRow[], nowMs = Date.now()): string[] {
if (rows.length === 0) return [];
const healthy = rows.filter((r) => isHealthy(r.health));
const pool = healthy.length > 0 ? healthy : rows;
const live = rows.filter((r) => isPoolMember(r.health));
const pool = live.length > 0 ? live : rows;
if (pool.length === 1) return [pool[0]!.ip];
const sorted = [...pool].sort((a, b) => a.ip.localeCompare(b.ip));
@@ -30,7 +30,7 @@ import { syncServiceToVpsTracker } from "./vps-tracker-sync.js";
import { fireEnsureHealthWorker, DEFAULT_HEALTH_FALLBACKS } from "./health/health-worker-deploy.js";
import {
canApplyLb,
isHealthy,
isPoolMember,
isSharedPool,
resolveDesiredAIps,
selectActiveIpsByMode,
@@ -287,7 +287,7 @@ function getGroupLbState(
} else {
existing.weight += weight;
existing.priority = Math.min(existing.priority, priority);
if (isHealthy(existing.health) && status && !isHealthy(status.status as IpHealthState)) {
if (isPoolMember(existing.health) && status && !isPoolMember(status.status as IpHealthState)) {
existing.health = status.status as IpHealthState;
}
}