fix and update

This commit is contained in:
lendry
2026-06-30 11:28:10 +03:00
parent c082b087c5
commit df4bbba133
6 changed files with 139 additions and 69 deletions

View File

@@ -677,13 +677,15 @@ export function buildSystemSettingPayload(setting: Pick<SystemSetting, 'key' | '
}
const GATEWAY_RETRY_STATUS = new Set([502, 503, 504]);
const GATEWAY_RETRY_ATTEMPTS = 2;
const GATEWAY_RETRY_ATTEMPTS = 4;
const GATEWAY_PROBE_MAX_ATTEMPTS = 48;
const GATEWAY_PROBE_BASE_DELAY_MS = 450;
const API_MAX_CONCURRENT = 8;
const API_BURST_MAX_CONCURRENT = 3;
const API_BURST_WINDOW_MS = 3000;
const GATEWAY_STABLE_PROBES = 3;
const API_MAX_CONCURRENT = 6;
const API_BURST_MAX_CONCURRENT = 2;
const API_BURST_WINDOW_MS = 5000;
const API_READY_EVENT = 'idp-api-ready';
const API_NOT_READY_EVENT = 'idp-api-not-ready';
function sleep(ms: number): Promise<void> {
return new Promise((resolve) => setTimeout(resolve, ms));
@@ -703,6 +705,9 @@ export function resetApiGatewayWarmup() {
export function invalidateApiGatewayReady() {
gatewayReadyResolved = false;
gatewayReadyAt = 0;
if (typeof window !== 'undefined') {
window.dispatchEvent(new CustomEvent(API_NOT_READY_EVENT));
}
}
export function isApiGatewayReady() {
@@ -728,6 +733,37 @@ export function subscribeApiReady(listener: () => void): () => void {
return () => window.removeEventListener(API_READY_EVENT, listener);
}
export function subscribeApiNotReady(listener: () => void): () => void {
if (typeof window === 'undefined') return () => undefined;
window.addEventListener(API_NOT_READY_EVENT, listener);
return () => window.removeEventListener(API_NOT_READY_EVENT, listener);
}
/** Несколько подряд успешных /health через nginx — защита от «один OK, потом 502». */
export async function waitForStableGateway(stableProbes = GATEWAY_STABLE_PROBES): Promise<void> {
if (typeof window === 'undefined') return;
gatewayReadyResolved = false;
gatewayReadyAt = 0;
let streak = 0;
for (let attempt = 0; attempt < 40 && streak < stableProbes; attempt += 1) {
if (await probeGatewayAlive()) {
streak += 1;
if (streak >= stableProbes) {
markApiGatewayReady();
return;
}
await sleep(400);
} else {
streak = 0;
await sleep(Math.min(500 + attempt * 220, 2200));
}
}
throw new ApiError('Сервер API временно недоступен. Попробуйте обновить страницу.', 503, 'GATEWAY_UNAVAILABLE');
}
async function acquireApiSlot(): Promise<void> {
const inBurst = gatewayReadyAt > 0 && Date.now() - gatewayReadyAt < API_BURST_WINDOW_MS;
const limit = inBurst ? API_BURST_MAX_CONCURRENT : API_MAX_CONCURRENT;
@@ -807,14 +843,24 @@ async function fetchWithGatewayRetry(url: string, init: RequestInit): Promise<Re
markApiGatewayReady();
}
if (GATEWAY_RETRY_STATUS.has(response.status) && attempt + 1 < GATEWAY_RETRY_ATTEMPTS) {
await sleep(gatewayRetryDelay(attempt));
invalidateApiGatewayReady();
try {
await waitForStableGateway(2);
} catch {
await sleep(gatewayRetryDelay(attempt));
}
continue;
}
return response;
} catch (error) {
lastNetworkError = error;
if (attempt + 1 < GATEWAY_RETRY_ATTEMPTS) {
await sleep(gatewayRetryDelay(attempt));
invalidateApiGatewayReady();
try {
await waitForStableGateway(2);
} catch {
await sleep(gatewayRetryDelay(attempt));
}
continue;
}
throw mapFetchError(error);