Files
OmniRoute/open-sse/utils/resourcePressure.ts
Xiangzhe a61020153c feat(admission): add adaptive overload and pressure controls
Add bounded weighted admission with fair queuing, deadline and cancellation handling, exact lease accounting, and a default-shadow runtime. Keep asynchronous resource-pressure shedding as an independent safety fuse and bound request feature estimation.
2026-08-05 08:32:38 -03:00

250 lines
7.5 KiB
TypeScript

import { checkHeapPressureGuard, HEAP_PRESSURE_THRESHOLD_MB } from "./heapPressure.ts";
import { buildErrorBody } from "./error.ts";
import {
createResourcePressureTracker,
resolveResourcePressureThresholds,
type PressureReason,
type ResourcePressureState,
type ResourcePressureThresholds,
type ResourceSignals,
} from "./resourcePressurePolicy.ts";
import {
sampleResourceSignals,
type SampleResourceSignalsDeps,
} from "./resourcePressureSampler.ts";
const MB = 1024 * 1024;
const RETRY_AFTER_SECONDS = "5";
const PRESSURE_MESSAGE = "Service temporarily unavailable due to resource pressure. Retry shortly.";
export type ResourcePressureGuardResult = {
success: false;
status: 503;
error: string;
response: Response;
};
export type ResourcePressureObservation = {
signals: ResourceSignals | null;
state: ResourcePressureState;
};
export type ResourcePressureRuntimeOptions = {
thresholds?: Partial<ResourcePressureThresholds>;
heapThresholdMb?: number | null;
immediateHeapUsedMb?: () => number;
sample?: () => Promise<ResourceSignals>;
nowMs?: () => number;
schedule?: (refresh: () => void) => void;
staleAfterMs?: number;
maxStaleMs?: number;
retryAfterMs?: number;
samplerDeps?: SampleResourceSignalsDeps;
};
export type ResourcePressureRuntime = {
check: () => ResourcePressureGuardResult | null;
getObservation: () => ResourcePressureObservation;
whenRefreshSettled: () => Promise<void>;
dispose: () => void;
};
function emptyState(): ResourcePressureState {
return {
severity: "normal",
reason: "none",
elevatedStreak: 0,
recoveryStreak: 0,
lastTransitionAtMs: 0,
observedAtMs: 0,
};
}
function requireDuration(name: string, value: number): number {
if (!Number.isFinite(value) || !Number.isInteger(value) || value < 0 || value > 3_600_000) {
throw new RangeError(`${name} must be an integer between 0 and 3600000`);
}
return value;
}
function buildCriticalGuard(reason: PressureReason): ResourcePressureGuardResult {
console.warn(
`[resourcePressure] critical pressure guard tripped (reason=${reason}); returning 503`
);
return {
success: false,
status: 503,
error: PRESSURE_MESSAGE,
response: new Response(
JSON.stringify(
buildErrorBody(503, PRESSURE_MESSAGE, undefined, {
type: "server_error",
code: "resource_pressure",
})
),
{
status: 503,
headers: { "Content-Type": "application/json", "Retry-After": RETRY_AFTER_SECONDS },
}
),
};
}
function immediateHeapGuard(
heapUsedMb: number,
thresholdMb: number | null
): ResourcePressureGuardResult | null {
if (thresholdMb == null) return null;
const guard = checkHeapPressureGuard(heapUsedMb, thresholdMb);
if (!guard) return null;
return buildCriticalGuard("v8_heap_absolute");
}
export function createResourcePressureRuntime(
options: ResourcePressureRuntimeOptions = {}
): ResourcePressureRuntime {
const heapThresholdMb =
options.heapThresholdMb === undefined ? HEAP_PRESSURE_THRESHOLD_MB : options.heapThresholdMb;
if (heapThresholdMb !== null && (!Number.isFinite(heapThresholdMb) || heapThresholdMb <= 0)) {
throw new RangeError("heapThresholdMb must be positive and finite or null");
}
const thresholds = resolveResourcePressureThresholds({
...options.thresholds,
heapAbsoluteThresholdMb:
options.thresholds?.heapAbsoluteThresholdMb === undefined
? null
: options.thresholds.heapAbsoluteThresholdMb,
});
const staleAfterMs = requireDuration("staleAfterMs", options.staleAfterMs ?? 1_000);
const maxStaleMs = requireDuration("maxStaleMs", options.maxStaleMs ?? 30_000);
const retryAfterMs = requireDuration("retryAfterMs", options.retryAfterMs ?? 1_000);
if (maxStaleMs < staleAfterMs) {
throw new RangeError("maxStaleMs must be greater than or equal to staleAfterMs");
}
const nowMs = options.nowMs ?? Date.now;
const immediateHeapUsedMb =
options.immediateHeapUsedMb ?? (() => process.memoryUsage().heapUsed / MB);
const sample = options.sample ?? (() => sampleResourceSignals(options.samplerDeps));
const schedule =
options.schedule ??
((refresh) => {
const handle = setImmediate(refresh);
handle.unref();
});
const tracker = createResourcePressureTracker(thresholds);
let lastSignals: ResourceSignals | null = null;
let state = emptyState();
let lastRefreshAtMs = Number.NEGATIVE_INFINITY;
let nextRefreshAtMs = Number.NEGATIVE_INFINITY;
let scheduled = false;
let inFlight: Promise<void> | null = null;
let disposed = false;
const refresh = (): void => {
if (disposed || inFlight) return;
scheduled = false;
inFlight = Promise.resolve()
.then(sample)
.then((signals) => {
if (disposed) return;
const settledAtMs = nowMs();
lastSignals = signals;
state = tracker.observe(signals);
lastRefreshAtMs = settledAtMs;
nextRefreshAtMs = settledAtMs + staleAfterMs;
})
.catch(() => {
if (!disposed) nextRefreshAtMs = nowMs() + retryAfterMs;
})
.finally(() => {
inFlight = null;
});
};
const scheduleRefresh = (): void => {
if (disposed || scheduled || inFlight) return;
scheduled = true;
schedule(refresh);
};
return {
check() {
let heapUsedMb = 0;
try {
heapUsedMb = immediateHeapUsedMb();
} catch {
heapUsedMb = 0;
}
const immediate = immediateHeapGuard(heapUsedMb, heapThresholdMb);
const now = nowMs();
if (now >= nextRefreshAtMs) scheduleRefresh();
if (immediate) {
state = {
severity: "critical",
reason: "v8_heap_absolute",
elevatedStreak: 0,
recoveryStreak: 0,
lastTransitionAtMs: now,
observedAtMs: now,
};
return immediate;
}
const cacheAge = lastSignals ? Math.max(0, now - lastRefreshAtMs) : Number.POSITIVE_INFINITY;
return cacheAge <= maxStaleMs && state.severity === "critical"
? buildCriticalGuard(state.reason)
: null;
},
getObservation: () => ({ signals: lastSignals, state }),
whenRefreshSettled: async () => {
if (scheduled) await new Promise<void>((resolve) => setImmediate(resolve));
if (inFlight) await inFlight;
},
dispose() {
disposed = true;
scheduled = false;
},
};
}
let defaultRuntime = createResourcePressureRuntime();
export function checkResourcePressureGuard(): ResourcePressureGuardResult | null {
return defaultRuntime.check();
}
export function getResourcePressureObservation(): ResourcePressureObservation {
return defaultRuntime.getObservation();
}
/** Replaces and disposes the process singleton when configuration is reloaded. */
export function reloadResourcePressureRuntime(
options: ResourcePressureRuntimeOptions = {}
): ResourcePressureRuntime {
defaultRuntime.dispose();
defaultRuntime = createResourcePressureRuntime(options);
return defaultRuntime;
}
export type {
PressureReason,
PressureSeverity,
ResourceMetricBytes,
ResourcePressureState,
ResourcePressureThresholds,
ResourcePressureTracker,
ResourceSignals,
} from "./resourcePressurePolicy.ts";
export {
classifyAdaptiveResourcePressure as classifyResourcePressure,
createResourcePressureTracker,
resolveResourcePressureThresholds,
} from "./resourcePressurePolicy.ts";
export {
sampleResourceSignals,
sanitizeMemoryBytes,
type ResourcePressureFs,
type SampleResourceSignalsDeps,
} from "./resourcePressureSampler.ts";