Skip to content

feat(http,oss): expose max idle timeout for pooled connections - #1734

Open
Moonpuck wants to merge 1 commit into
alibaba:mainfrom
Moonpuck:feat/http-pool-max-idle-timeout
Open

Moonpuck wants to merge 1 commit into
alibaba:mainfrom
Moonpuck:feat/http-pool-max-idle-timeout

Conversation

@Moonpuck

@Moonpuck Moonpuck commented Nov 9, 2026

Copy link
Copy Markdown
Collaborator

What this does

The socket pool already expires idle connections, but there was no way to ask
for it: the HTTP client always built its pool with the default expiration
(-1ULL, never), and objstore::ClientOptions had no knob for it either.

This exposes it end to end:

  • Client::set_max_idle_timeout() / Client::max_idle_timeout(), captured by
    each vCPU's built-in dialer on its first dial
  • new_http_dialer() forwards the expiration to its PoolDialer
  • objstore::ClientOptions::max_idle_timeout_us, passed down to the HTTP client

Default stays -1ULL (never expire), so existing behaviour is unchanged.

Hardening: a zero expiration is now rejected

SocketPoolArgs::expiration == 0 was harmless before only because nothing ever
passed it. If it reaches the pool, collect() computes
interval = min(heartbeat_interval, 0) = 0 and check_expire_heartbeat() trips
assert(near_expire.expiration() > now) -- or spins the collector burning a
vCPU in release builds, where the assert is compiled out.

Now that the value is user-reachable, TCPSocketPool's constructor normalizes
0 to -1ULL. Clamping there rather than in new_http_dialer() covers every
caller of the public new_tcp_socket_pool() API, not just the HTTP path.

Tests

net/http/test/client_function_test.cpp:

  • max_idle_timeout_expires_pooled_connection
  • zero_max_idle_timeout_means_no_expiration

As a negative control, removing the clamp makes the second test abort with
Assertion failed: (near_expire.expiration() > now), function check_expire_heartbeat, file net/pooled_socket.cpp.

Local (macOS / kqueue, Debug):

  • client_function_test: 55 passed, 1 skipped
  • dialer_test: 17 passed
  • test-sockpool: 32 passed

Note

There is a separate, hand-written PR against release/0.9 for the same feature.
It is not a cherry-pick: 0.9 has no net/http/dialer.*, and its pool is a
thread_local PooledDialer inside client.cpp, so the plumbing and the test
setup are both different. Please do not add need-backport here -- the
automatic cherry-pick would conflict on dialer.{h,cpp} (modify/delete) and
commit the conflict markers into the generated PR.

The socket pool already expires idle connections, but the HTTP client always
built its pool with the default expiration (-1ULL, never) and the OSS
ClientOptions had no knob for it.

- Client::set_max_idle_timeout()/max_idle_timeout(), captured by each vCPU's
  built-in dialer on its first dial
- new_http_dialer() forwards the expiration to its PoolDialer
- objstore::ClientOptions::max_idle_timeout_us, passed to the HTTP client

A zero expiration is now rejected as -1ULL in the TCPSocketPool constructor.
Zero was harmless before only because nothing ever passed it: collect() would
compute interval = min(heartbeat_interval, 0) = 0 and check_expire_heartbeat()
would trip assert(near_expire.expiration() > now), or spin the collector in
release builds. Clamping in the pool rather than in new_http_dialer() covers
every caller of the public new_tcp_socket_pool() API, not just the HTTP path.

Test: net/http/test/client_function_test.cpp
  max_idle_timeout_expires_pooled_connection
  zero_max_idle_timeout_means_no_expiration
@Moonpuck
Moonpuck requested review from Coldwings and lihuiba November 9, 2026 05:57
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