Skip to content

feat(client): add ClientBuilder::happy_eyeballs_timeout option - #3081

Open
jeffyaw wants to merge 1 commit into
seanmonstar:masterfrom
jeffyaw:happy-eyeballs-timeout
Open

feat(client): add ClientBuilder::happy_eyeballs_timeout option#3081
jeffyaw wants to merge 1 commit into
seanmonstar:masterfrom
jeffyaw:happy-eyeballs-timeout

Conversation

@jeffyaw

@jeffyaw jeffyaw commented Aug 4, 2026

Copy link
Copy Markdown

feat(client): add ClientBuilder::happy_eyeballs_timeout option

What

Exposes hyper-util's HttpConnector::set_happy_eyeballs_timeout on both the
async and blocking ClientBuilder:

let client = reqwest::Client::builder()
    .happy_eyeballs_timeout(Some(Duration::from_millis(50)))
    .build()?;

None disables the parallel fallback entirely (addresses are tried
sequentially); the default stays at hyper-util's 300ms, so behavior is
unchanged for existing users. The plumbing follows the exact shape of
tcp_user_timeout (#2724) and the TCP keepalive options (#2675): a Config
field in src/async_impl/client.rs, a setter on ConnectorBuilder in
src/connect.rs that forwards to the underlying HttpConnector in all three
TLS variants, and a with_inner mirror in src/blocking/client.rs.

Why

hyper-util's 300ms default is a real, measurable tax on dual-stack hosts
where the preferred family does not answer:

  • Concrete case, measured: a dual-stack localhost where the server is bound
    only to ::1. The IPv4 attempt is preferred and never completes, so the
    client waits out the full 300ms before trying ::1, which then connects
    immediately. Node, whose equivalent knob
    (autoSelectFamilyAttemptTimeout) defaults to 250ms and is user-settable,
    does the same request in ~15ms. Once per fresh connection.
  • The fallback is already error-driven, so this only bites when the preferred
    family hangs rather than refusing -- but that is exactly the case a user
    cannot work around from outside the connector.

Related: #1318 ("Happy Eyeballs support", closed 2021) — that report turned
out to be an environment with no IPv6 routes at all, but the thread also
asked how to reach set_happy_eyeballs_timeout from reqwest, which is still
not possible today.

Since hyper-util already implements the timeout and its setter, reqwest just
needs to pass it through -- no new connection logic.

Non-goals

The HTTP/3 connector (src/async_impl/h3_client/connect.rs) has its own
separate happy-eyeballs implementation by design and is deliberately not
touched here; this option configures the TCP (HttpConnector) path only.

Expose hyper-util's HttpConnector::set_happy_eyeballs_timeout through
the async and blocking client builders, so users can tune or disable
the RFC 6555 dual-stack fallback delay (default 300ms).

Closes seanmonstar#1318

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
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