Skip to content

fix: pool Hive API connections in a shared keep-alive session - #18

Open
danceratopz wants to merge 1 commit into
ethereum:mainfrom
danceratopz:enginex-port-exhaustion
Open

fix: pool Hive API connections in a shared keep-alive session#18
danceratopz wants to merge 1 commit into
ethereum:mainfrom
danceratopz:enginex-port-exhaustion

Conversation

@danceratopz

@danceratopz danceratopz commented Aug 7, 2026

Copy link
Copy Markdown
Member

Replace the bare requests.post/requests.delete calls in HiveTestSuite.start/end, HiveTest.start/end and HiveTest.register_multi_test_client with a single process-wide requests.Session mounting an HTTPAdapter with a urllib3.Retry policy (connect-error retries with exponential backoff).

Why

See recent sporadic fails on the enginex dashboard:

Each bare call opens a fresh TCP connection and leaves the socket in TIME_WAIT for 60 s. Ephemeral-port exhaustion is per destination tuple, and every Hive API call targets the one fixed endpoint, so all churn piles onto a single (dst_ip, dst_port). At consume-enginex throughput (~110–145 tests/s on the dashboard host × 3 calls/test ≈ 330–440 connections/s) the ~28k-port ephemeral range toward the Hive API fills in ~90 s and connect() intermittently fails with OSError: [Errno 99] (EADDRNOTAVAIL) on all xdist workers at once. In the 2026-07-18/19 nethermind eels/consume-enginex dashboard runs this produced 4–5 spurious "Test was terminated by host" failures and silently dropped 85/40 test results.

Pooling collapses ~3 connections/test (~163k per full run) into a few persistent keep-alive sockets — removing the mechanism rather than raising the ceiling.

Retry semantics

Only connect errors are retried (5 attempts, 0.5 s backoff factor, ~15 s total): they are raised before the request is sent, so a retry cannot duplicate a non-idempotent POST (no double start_test). Read errors and HTTP error statuses are not retried.

Keep-alive caveat (verified)

Pooling only helps if the server honors keep-alive. Hive's Go backend serves the sim API with a stock net/http server (internal/libhive/run.go: &http.Server{Handler: tm.API()}) — keep-alives on by default, no idle timeout, no handler emits Connection: close. Confirmed empirically below.

Validation

Hive --dev + nethermind 1.40.0-unstable, 12,888 consume-enginex Cancun tests at -n 4, sampling ss -tan state time-wait '( dport = :3000 )':

Metric 0.1.0a5 (per-call) this PR (pooled)
Verdicts 12,888 passed 12,888 passed
Peak TIME_WAIT → hive API 9,684 66
Established → hive API ≤4 transient 8–9 persistent
Wall clock 36:04 36:58 (noise; perf ≈ neutral)

Notes

  • One session per process = one per pytest-xdist worker; urllib3's pool is thread-safe.
  • HiveTestSuite/HiveTest dataclasses unchanged (still asdict/JSON round-trippable, as execution-specs relies on).
  • urllib3>=1.26.0,<3 declared explicitly (>=1.26.0 for the modern Retry kwargs; <3 congruent with requests>=2.31.0, and the 2.x line is what CI and execution-specs actually resolve).
  • New unit tests (src/hive/tests/test_session.py) run against a local in-process HTTP server — no hive required: singleton behavior, retry config, 42-calls-over-1-connection reuse, connect-retry until a late-starting server is up.

Release

Propose tagging v0.1.0a6 after merge (setuptools-scm; move the CHANGELOG [Unreleased] entry under v0.1.0a6); execution-specs then bumps ethereum-hive>=0.1.0a5,<1.0.0>=0.1.0a6,<1.0.0.

Replace the bare requests.post/requests.delete calls in HiveTestSuite
start/end, HiveTest start/end and register_multi_test_client with a
process-wide requests.Session mounting an HTTPAdapter with a
urllib3.Retry policy (connect-error retries with exponential backoff).

Per-call connections leave a TIME_WAIT socket per request toward the
single fixed Hive API endpoint; at consume-enginex throughput
(>100 tests/s, three API calls per test) this exhausts the simulator's
ephemeral port range toward that endpoint and connect() fails with
EADDRNOTAVAIL (Errno 99), sporadically failing and silently dropping
tests. Pooling collapses ~3 connections per test into a handful of
persistent keep-alive sockets, removing the mechanism.

Only connect errors are retried: they are raised before the request is
sent, so retrying cannot duplicate a non-idempotent POST.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant