fix(client): pace connection establishment; auto warmup claims 25% of the port budget#411
fix(client): pace connection establishment; auto warmup claims 25% of the port budget#411viraatc wants to merge 3 commits into
Conversation
… budget The full-auto config (max_connections=-1, warmup_connections=-1) resolved warmup to half the pool, and the pool ceiling to the whole ephemeral-port budget — so init fired ~half the port range as one unbounded connect burst, and max_throughput's t=0 wave could stampede the pool the same way. - ConnectionPool paces establishment with a connect limiter (MAX_CONCURRENT_CONNECTS=128 in-flight per pool); applies to warmup and runtime pool growth, never to the pooled-connection reuse path. _creating is reserved before waiting on the limiter so the max_connections ceiling cannot be overshot by queued acquires. - warmup_connections=-1 with max_connections=-1 now resolves to 25% of the port budget (AUTO_WARMUP_BUDGET_FRACTION); the ceiling stays at the full budget and the rest of the pool opens on demand at runtime. Explicit values are untouched; worker-side 50%-of-pool auto still applies when max_connections is explicit. - Remove dead get_used_port_count/get_ephemeral_port_limit helpers and fix stale ENDPOINT_CLIENT.md auto-configuration docs. - YAML templates regenerated by the pre-commit hook (also picks up entries stale from an earlier schema change). Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
|
MLCommons CLA bot All contributors have signed the MLCommons CLA ✍️ ✅ |
There was a problem hiding this comment.
Code Review
This pull request refactors the connection pool and client configuration to improve connection warmup and pacing, and reorganizes timeout settings in the configuration templates. Specifically, it introduces a connection pacing mechanism (MAX_CONCURRENT_CONNECTS) using an asyncio.Semaphore to prevent SYN floods and ephemeral port exhaustion during bursts. It also changes how max_connections=-1 and warmup_connections=-1 are resolved, using a static fraction of the ephemeral port budget (AUTO_WARMUP_BUDGET_FRACTION) instead of dynamically subtracting live socket occupancy, which was racy. Unused port-counting utility functions were removed, and corresponding unit tests and documentation were updated. Finally, global timeout and drain configurations in the YAML templates were consolidated under a new timeouts section. I have no feedback to provide as there are no review comments.
Important
The consumer version of Gemini Code Assist on GitHub is being sunset. Starting June 18, 2026, new organization installations will be blocked, and all code review activity will officially cease on July 17, 2026.
For more details on the timeline and next steps, please review the Help Documentation.
The previous commit's template regeneration ran the pre-commit hook's bare `python` entry, which resolved a different checkout's inference_endpoint package and emitted fields this schema rejects (settings.timeouts / settings.metrics). Regenerating with the repo venv reproduces the original templates byte-for-byte; all templates round-trip through BenchmarkConfig.from_yaml_file again. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
A bare `python` entry resolves whatever is first on PATH; if that interpreter has inference_endpoint installed from a sibling checkout, the hook silently regenerates templates against the wrong schema. Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
Problem
Since #386, the full-auto config (
max_connections=-1,warmup_connections=-1) resolves the pool ceiling to the entire ephemeral-port budget (range × distinct_endpoints, ~28k on default Linux). Auto warmup derives from that ceiling (connections_per_worker // 2), andConnectionPool.warmup()fires the whole count as one unboundedasyncio.gather— so a default run opens ~half the port range as one simultaneous connect burst at t=0 (~14k SYNs to a single endpoint). Themax_throughputt=0 wave can grow the pool the same unbounded way throughacquire().That burst is what regressed real runs: it can overflow the server accept queue (SYN drops → seconds of RTO retransmits) and transiently exhaust client ephemeral ports (
EADDRNOTAVAIL), and a failed connect currently surfaces as a failed query.An alternative mitigation was proposed in #407 (halve the auto
max_connections). That helps empirically, but it works by indirectly halving the warmup burst: the ceiling only counts live pool members (established + creating), so it cannot bound TIME_WAIT/port pressure in principle — and it permanently halves the client's steady-state connection roofline (silently: excess demand just queues in the pool) to fix a startup-time event. It also loses its protection the moment someone sets an explicitmax_connections, since warmup scales off it.Fix
Keep the full auto ceiling; fix the burst directly:
ConnectionPoolbounds in-flightconnect()calls with a connect limiter (MAX_CONCURRENT_CONNECTS = 128per pool). This covers both warmup and runtime pool growth, and never touches the pooled-connection reuse path. Connects complete in ~1 RTT, so establishment now proceeds in fast waves instead of one flood — total warmup time is barely affected._creatingis reserved before waiting on the limiter so queued acquires can't overshoot themax_connectionsceiling.max_connectionsandwarmup_connectionsare-1, warmup resolves to 25% of the port budget (AUTO_WARMUP_BUDGET_FRACTION) instead of half the pool (= half the port range). The ceiling stays at the full budget; connections beyond the warm set are opened on demand at runtime (paying the connect cost lazily, now paced). Warming far beyond steady-state concurrency only wastes ports — the LIFO idle stack means excess warm connections sit established at the bottom holding ephemeral ports until shutdown — and inflates the startup burst.get_used_port_count()/get_ephemeral_port_limit()helpers (unused since fix: dont discount open tcp-ports against max-connections on init #386); fixesdocs/ENDPOINT_CLIENT.md's auto-configuration section, which still described the pre-fix: dont discount open tcp-ports against max-connections on init #386 live-socket subtraction and a wrong 90% figure formin_required_connections(code: 12.5%); pins theregenerate-templatespre-commit hook to the project env viauv run --no-sync(a barepythonentry resolves whatever is first on PATH and can regenerate templates against a sibling checkout's schema — which briefly happened on this very branch and was reverted in09fa7b95).Unchanged behavior:
max_connectionsand explicitwarmup_connectionsvalues are untouched (over-budget guard unchanged).max_connections, auto warmup remains 50% of the pool (worker-side).mainand round-tripped throughBenchmarkConfig.from_yaml_file).Notes
connect()time as anOSErrorwith no automatic retry — a bounded EADDRNOTAVAIL retry in_create_connectionis a natural follow-up to turn transient port pressure into backpressure instead of a failed query (MLPerf run-validity relevance).128 × num_workers(e.g. ~2k with 16 workers); the constant is deliberately conservative and easy to revisit with measured errno counts.Tests
TestAutoWarmupResolution(new): full-auto resolves warmup to 25% of the budget, scales with distinct endpoints, explicit/disabled warmup preserved,-1sentinel survives whenmax_connectionsis explicit.test_connect_pacing_bounds_inflight_creates(new): against the real echo server, peak in-flightconnect()never exceeds the limiter during warmup.TestEndpointBudgetScalingguard test (dead-helper patch removed).tests/unit/endpoint_client/: 79 passed. Template round-trip + template tests: 11 passed. Full unit suite: no new failures vsmain(an identical pre-existing set of xdist-parallel failures inmetrics_aggregatortests reproduces on cleanmain).pythonimports a sibling checkout,pre-commit run regenerate-templates --all-filesnow produces zero template diff.pre-commit run --all-filesgreen.🤖 Generated with Claude Code