Repository navigation
Conversation
This branch has not been deployed
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Fixes #8254.
Problem
The AI transport pool bounds TCP connections process-wide, but a connection pool is not request admission: one tenant streaming long answers can occupy the pool's capacity regardless of who else is waiting, and nothing distinguishes a trusted tenant from anonymous traffic when the gateway is saturated. This adds the first delivery of #8254: opt-in per-gateway concurrency admission with trusted-tenant and model-pool in-flight caps, finite bounded waiting inside the shared request deadline, and deterministic FIFO queue discipline — fixed caps, no adaptive control, no distributed quota claims.
Approach
Two independent pool dimensions gate an invocation, with separate ownership. The logical permit lives in the trusted-tenant pool: acquired once per invocation from the authenticated
AgentRequestContext(anonymous traffic shares one pool; the identity never comes from wire metadata), held across retries, fallbacks and repairs, and released exactly once at the terminal signal. The target permit lives in the model pool: acquired per attempt against the effective model — now carried on the prepared exchange instead of re-parsed from the upstream body — and released when that attempt ends; for a streaming response that is body completion, error or cancellation, so backoff between attempts holds no target capacity and a fallback attempt keys on the fallback model.Each pool is a fair semaphore with a finite waiting bound; arrivals beyond the bound are rejected immediately, and the effective wait is the smaller of the configured maximum and the shared-deadline remainder, so time already queued counts against
requestDeadline— the 3 s/2.8 s primary scenario cannot grant a fresh 5 s allowance. Every permit decision runs at subscription time, never at assembly, so a retry attempt observes its predecessor's release. Waiting happens off the event loop on the bounded-elastic scheduler, bounded by the waiting limits. The pool map is hard-capped and the in-flight/waiting gauges aggregate per pool in-process — raw tenants and models are never exported as metric labels.Rejections happen before any upstream call: a waiting-bound overflow or an expired wait surfaces as 429 TOO_MANY_REQUESTS with distinct attempt-event reasons (
ADMISSION_REJECTED,ADMISSION_WAIT_DEADLINE_EXCEEDED) and the terminal observation asRATE_LIMITED. With the feature disabled the gateway holds no admission state and behaves byte-identically to today; the admission component only exists whenshenyu.plugins.ai.proxy.admission.enabled=true, and a misconfigured bound fails fast.Testing
Deterministic tests cover the pool semantics (acquire/release restoring the pool, immediate rejection at a full pool without a waiting bound, the full waiting bound rejecting the third arrival while the holder keeps its permit, a waiter admitted on release, an expired deadline rejecting before any waiting, anonymous sharing, independent tenant and model pools, the pool-map cap, unknown-pool release as a no-op, fail-fast bounds) and the orchestration (a full tenant pool rejecting without any upstream call, the 429 mapping, retry re-acquiring the target permit with both pools restored at the terminal signal, the model pool capping attempts from different tenants, cancellation releasing both held permits). All are sleep-free; timing-sensitive release assertions use bounded awaitility.
shenyu-plugin-ai-proxy526 tests (16 new),shenyu-spring-boot-starter-plugin-ai-proxy22,shenyu-common711 (4 skipped) — all green; Checkstyle clean. The two-pool ownership table, deadline semantics and layering against the Netty transport pool are documented inshenyu-plugin-ai/AI_CONCURRENCY_ADMISSION.md.