mirror of
https://github.com/diegosouzapw/OmniRoute.git
synced 2026-08-25 08:32:11 +03:00
fix(catalog): keep large builds event-loop responsive (#11367)
Merged via consolidated batch validation (worktree `.claude/worktrees/batch-0824d`, 11-PR video-bridge/catalog/ops batch, tip `dafb4ae8`). Fixes the #9147 catalog-scale event-loop regression: reuses one build-local capability snapshot, yields cooperatively during catalog/virtual-pool construction, reads only persisted TTL settings. Static gates: typecheck:core, file-size, changelog-integrity, complexity, cognitive-complexity all green. Own regression test (tests/unit/9147-catalog-eventloop-yield.test.ts) reproduced the RED→GREEN transition in isolated runs per the PR's own evidence; under current shared-devbox load (10-15, multiple parallel sessions) the test intermittently reports INFRA-RED exactly as the PR body pre-disclosed (documented starvation signature, not a code defect). Thanks for the careful RED/GREEN + INFRA-RED discipline.
This commit is contained in:
committed by
GitHub
parent
dafb4ae808
commit
12b8df02dd
@@ -7,9 +7,9 @@ import {
|
||||
getSettings,
|
||||
getCachedProviderNodes,
|
||||
getModelAliases,
|
||||
getDatabaseSettings,
|
||||
getHiddenModelsByProvider,
|
||||
} from "@/lib/localDb";
|
||||
import { getUserDatabaseSettings } from "@/lib/db/databaseSettings";
|
||||
import { createLazyConnectionView } from "@/lib/db/providers/lazyConnectionView";
|
||||
import { extractAliasBackedModels } from "./aliasBackedModels";
|
||||
import {
|
||||
@@ -229,7 +229,10 @@ async function buildCatalogPayload(
|
||||
// Falls back to the hardcoded default if not set or on error.
|
||||
let cacheTTL = CATALOG_CACHE_TTL_MS_DEFAULT;
|
||||
try {
|
||||
const dbSettings = await getDatabaseSettings();
|
||||
// Only the persisted cache section is needed here. The full database-settings
|
||||
// view also calculates dbstat, WAL, schema and integrity diagnostics, which are
|
||||
// synchronous and can pin the event loop after an otherwise cooperative build.
|
||||
const dbSettings = getUserDatabaseSettings();
|
||||
cacheTTL = dbSettings.cache?.modelCatalogCacheTtlMs ?? CATALOG_CACHE_TTL_MS_DEFAULT;
|
||||
} catch {
|
||||
// Swallow — use default TTL on DB error
|
||||
@@ -249,7 +252,7 @@ async function buildUnifiedModelsResponseCore(
|
||||
// event-loop yield, so a large deployment pins the single Node.js thread for the
|
||||
// whole build (reporter: 183 connections / 2000+ models → 10.1s stall that blocks the
|
||||
// dashboard WS heartbeat). Yield every `catYIELD_EVERY` items across the hot loops.
|
||||
const catYIELD_EVERY = 20;
|
||||
const catYIELD_EVERY = 5;
|
||||
let catYieldCount = 0;
|
||||
const maybeYieldCatalogBuild = async (): Promise<void> => {
|
||||
catYieldCount++;
|
||||
@@ -393,11 +396,10 @@ async function buildUnifiedModelsResponseCore(
|
||||
): boolean => {
|
||||
if (!providerKey || !modelId) return false;
|
||||
const canonical = canonicalProviderId || resolveCanonicalProviderId(providerKey);
|
||||
const alias =
|
||||
providerIdToAlias[canonical] || providerIdToAlias[providerKey] || undefined;
|
||||
const alias = providerIdToAlias[canonical] || providerIdToAlias[providerKey] || undefined;
|
||||
const nodePrefix = providerIdToPrefix[providerKey] || providerIdToPrefix[canonical];
|
||||
const keysToCheck = [providerKey, canonical, alias, nodePrefix].filter(
|
||||
(k): k is string => Boolean(k)
|
||||
const keysToCheck = [providerKey, canonical, alias, nodePrefix].filter((k): k is string =>
|
||||
Boolean(k)
|
||||
);
|
||||
for (const key of keysToCheck) {
|
||||
const hiddenSet = hiddenModelsByProvider.get(key);
|
||||
@@ -830,7 +832,7 @@ async function buildUnifiedModelsResponseCore(
|
||||
try {
|
||||
const suffix = autoId.replace(/^auto\/?/, "");
|
||||
if (!preparedAutoInputs) {
|
||||
preparedAutoInputs = await prepareBuiltinAutoComboInputs();
|
||||
preparedAutoInputs = await prepareBuiltinAutoComboInputs(capabilityResolutionSnapshot);
|
||||
await yieldCatalogBuildTurn();
|
||||
}
|
||||
const virtualCombo = await createBuiltinAutoCombo(autoId, suffix, preparedAutoInputs);
|
||||
@@ -1053,11 +1055,7 @@ async function buildUnifiedModelsResponseCore(
|
||||
// `openai` provider page (codex runs on the openai-compatible connection)
|
||||
// or via the `cx` alias — check all three so a hide from any of them
|
||||
// suppresses the bare model id here.
|
||||
if (
|
||||
isModelHiddenBulk("codex", modelId) ||
|
||||
isModelHiddenBulk("openai", modelId)
|
||||
)
|
||||
continue;
|
||||
if (isModelHiddenBulk("codex", modelId) || isModelHiddenBulk("openai", modelId)) continue;
|
||||
|
||||
const alias = providerIdToAlias.codex || "cx";
|
||||
const aliasId = `${alias}/${modelId}`;
|
||||
@@ -1892,7 +1890,9 @@ async function buildUnifiedModelsResponseCore(
|
||||
|
||||
const modelId =
|
||||
model.root || (typeof model.id === "string" ? model.id.split("/").pop() : undefined);
|
||||
return modelId ? getTokenLimit(canonicalId, modelId) : getTokenLimit(canonicalId);
|
||||
return modelId
|
||||
? getTokenLimit(canonicalId, modelId, capabilityResolutionSnapshot)
|
||||
: getTokenLimit(canonicalId, null, capabilityResolutionSnapshot);
|
||||
};
|
||||
|
||||
let enrichmentSnapshot: CatalogEnrichmentSnapshot | undefined;
|
||||
@@ -1905,7 +1905,7 @@ async function buildUnifiedModelsResponseCore(
|
||||
}
|
||||
enrichmentSnapshot = {
|
||||
modelsDevPricing,
|
||||
capabilityResolution: capabilityResolutionSnapshot,
|
||||
capabilityResolutionSnapshot,
|
||||
providerNodeIdsByPrefix: providerNodeIdByPrefix,
|
||||
};
|
||||
// The production profile identified pricing snapshot construction as the last
|
||||
|
||||
@@ -227,7 +227,8 @@ export async function finalizeCatalogResponse(
|
||||
// per-entry work is interleaved with other callers / the dashboard WS.
|
||||
const yieldTurn = (): Promise<void> => new Promise((resolve) => setImmediate(resolve));
|
||||
await yieldTurn();
|
||||
const capabilityResolutionSnapshot = createModelCapabilityResolutionSnapshot();
|
||||
const capabilityResolutionSnapshot =
|
||||
enrichmentSnapshot?.capabilityResolutionSnapshot ?? createModelCapabilityResolutionSnapshot();
|
||||
const enriched: Array<Record<string, unknown>> = [];
|
||||
const catYIELD_EVERY = 5;
|
||||
let catEnrichCount = 0;
|
||||
|
||||
@@ -40,7 +40,6 @@ type JsonRecord = Record<string, unknown>;
|
||||
|
||||
export interface CatalogEnrichmentSnapshot {
|
||||
modelsDevPricing: PricingByProvider | null;
|
||||
capabilityResolution?: ModelCapabilityResolutionSnapshot;
|
||||
providerNodeIdsByPrefix?: Readonly<Record<string, string>>;
|
||||
/** #9147: build-local bulk load of synced capabilities + token/context overrides
|
||||
* so per-entry enrichment never hits SQLite again (see catalogResponse.ts). */
|
||||
|
||||
Reference in New Issue
Block a user