mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-19 05:32:19 +03:00
* fix(sse): gate structural chat admission shedding on real heap pressure Closes #10183, Closes #10268 3.8.49 (#9654/#9940) replaced the 3.8.48 heap-ratio shed (heapUsed/heapLimit >= 0.75) in chatBodyAdmission.ts with an unconditional CHAT_MAX_HEAVY_IN_FLIGHT=1 structural lease. A second concurrent "structurally heavy" chat request (>=200 messages, >=64 tools, or >=32k estimated tokens — routine for coding-agent fan-out like Hermes/Cursor/Claude Code) was hard-rejected with a retryable HTTP 503 chat_admission_busy/structure_limit regardless of actual heap pressure, even on a host with ample free RAM. Restore the heap-conditional gate as an ADDITIONAL check layered on top of (not a replacement for) the #9654 bounded-concurrency / per-connection-lane protection: when heavyweight capacity is busy, only enter the bounded-wait/shed path when a live heap-pressure probe (heapUsed / v8 heap_size_limit >= OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO, default 0.75) confirms real pressure. A healthy heap now admits the second heavy request immediately via a no-op lease instead of parking or shedding it. The probe is injectable via admitChatStructure({ heapPressureCheck }) for deterministic tests. Regression tests: - tests/unit/bug-10183-admission-heavy-healthy-heap.test.ts (new, permanent): healthy-heap 2nd heavy request now admitted (was RED); genuinely pressured heap still sheds it. - tests/unit/probe-10268-structural-503.test.ts (promoted to permanent): the exact reported 503 chat_admission_busy shape is still produced under real heap pressure, and the same fan-out is admitted on a healthy heap. - tests/unit/chat-body-admission.test.ts, tests/unit/chat-body-admission-queue.test.ts, tests/unit/per-connection-admission-9654.test.ts updated to inject heapPressureCheck: () => true where they exercise the busy/shed path, preserving #9654/#4380 coverage. Gates run: npm run typecheck:core (clean), eslint --suppressions-location config/quality/eslint-suppressions.json on changed files (clean), scripts/check/check-file-size.mjs (OK), scripts/check/check-test-discovery.mjs (OK), focused admission suite (68/68 passing) and npm run test:unit (in progress at commit time under heavy shared-devbox contention from a 13-way parallel session fan-out; no admission-related failures observed through 1873 lines of output, the sole failure seen was a pre-existing unrelated proxy/search timeout consistent with known load-induced flakiness, not a regression from this change). ⚠️ base-red inherited: #9985 — ESLint errors (2) from #10250 * docs(env): document OMNIROUTE_CHAT_ADMISSION_HEAP_SHED_RATIO (#10183, #10268) * fix(sse): bound the healthy-heap admission fast path (#10437) The #10183/#10268 fix admitted a busy heavyweight request immediately whenever the heap was healthy, via an unconditional no-op lease with no bound of its own -- an unlimited number of "healthy heap" requests could pile in ahead of the heap-pressure shed path, defeating the point of admission control. Adds an independent, bounded healthy-heap headroom budget (CHAT_ADMISSION_HEALTHY_HEADROOM, tryAcquireHealthyHeadroom()) that the healthy-heap fast path draws from; once exhausted, requests fall through to the same bounded-wait/shed path used under real heap pressure, which is otherwise unchanged. Also fixes a pre-existing gap in per-connection-admission-9654.test.ts's shared-budget test, which needed an explicit heapPressureCheck override to keep exercising the #10110 invariant now that a healthy heap gets bounded headroom instead of an outright reject. * docs(env): document OMNIROUTE_CHAT_ADMISSION_HEALTHY_HEADROOM in .env.example Documented in docs/reference/ENVIRONMENT.md but missing from .env.example, caught by the env-doc-sync gate when combined with other PRs in the release merge-train. --------- Co-authored-by: adevwithpurpose <adevwithpurpose@users.noreply.github.com>
71 lines
3.3 KiB
TypeScript
71 lines
3.3 KiB
TypeScript
// #10268: "[BUG] API call failed (attempt 1/3): InternalServerError [HTTP 503]" — Hermes
|
|
// Agent / Cursor coding-agent fan-out landed on the same structural admission gate as
|
|
// #10183 and burned its 3 retries on OmniRoute's own `chat_admission_busy` 503, which it
|
|
// misread as an upstream capacity error. Same root cause, same fix (heap-conditional
|
|
// shedding in `admitChatStructure`): this test is the permanent regression guard proving
|
|
// the exact reported 503 shape is still produced when heap pressure is GENUINELY high,
|
|
// so the #4380 heap-amplification shed path is preserved rather than removed outright.
|
|
import { test } from "node:test";
|
|
import assert from "node:assert/strict";
|
|
import {
|
|
admitChatStructure,
|
|
ChatAdmissionController,
|
|
type ChatAdmissionLease,
|
|
} from "../../src/shared/middleware/chatBodyAdmission.ts";
|
|
|
|
function heavyBody() {
|
|
const messages = Array.from({ length: 201 }, (_, i) => ({ role: "user", content: `prompt ${i}` }));
|
|
const tools = Array.from({ length: 32 }, (_, i) => ({
|
|
type: "function",
|
|
function: { name: `tool_${i}`, description: "a".repeat(64), parameters: { type: "object" } },
|
|
}));
|
|
return { model: "grok-4.5-fast-high", messages, tools, stream: true };
|
|
}
|
|
|
|
test("#10268: 2nd structurally-heavy agent request is rejected 503 (chat_admission_busy) under real heap pressure", async () => {
|
|
const controller = new ChatAdmissionController(1);
|
|
const first = await admitChatStructure(heavyBody(), null, { controller, queueMs: 0 });
|
|
assert.equal(first.admit, true);
|
|
const lease = (first as { admit: true; lease: ChatAdmissionLease | null }).lease;
|
|
assert.ok(lease);
|
|
try {
|
|
const second = await admitChatStructure(heavyBody(), null, {
|
|
controller,
|
|
queueMs: 0,
|
|
// Simulate genuine heap pressure (#10183/#10268 fix: shedding is now
|
|
// conditional on this, not unconditional on capacity alone).
|
|
heapPressureCheck: () => true,
|
|
});
|
|
assert.equal(second.admit, false); // reported failure path, still reachable under real pressure
|
|
const res = (second as { admit: false; response: Response }).response;
|
|
assert.equal(res.status, 503); // client is shown HTTP 503
|
|
const body = await res.json();
|
|
assert.equal(body.error?.message, "Structurally heavy chat request capacity is busy; retry shortly.");
|
|
assert.equal(body.error?.code, "chat_admission_busy");
|
|
assert.equal(body.error?.reason, "structure_limit");
|
|
} finally {
|
|
lease.release();
|
|
}
|
|
});
|
|
|
|
test("#10268: 2nd structurally-heavy agent request is admitted on a healthy heap (the fix)", async () => {
|
|
const controller = new ChatAdmissionController(1);
|
|
const first = await admitChatStructure(heavyBody(), null, { controller, queueMs: 0 });
|
|
assert.equal(first.admit, true);
|
|
const lease = (first as { admit: true; lease: ChatAdmissionLease | null }).lease;
|
|
assert.ok(lease);
|
|
try {
|
|
const second = await admitChatStructure(heavyBody(), null, {
|
|
controller,
|
|
queueMs: 0,
|
|
// No override: default heap probe reads live process stats (healthy here),
|
|
// reproducing legitimate Hermes/Cursor fan-out traffic that must no longer
|
|
// be shed on ample free RAM.
|
|
});
|
|
assert.equal(second.admit, true, "healthy heap must admit legitimate agent fan-out");
|
|
if (second.admit) second.lease?.release();
|
|
} finally {
|
|
lease.release();
|
|
}
|
|
});
|