Proposal Details
Problem Statement:
The standard http.Client / http.Transport in Go does not expose all the control points you would need to fully manage these required aspects to supply a form of user specified client-side connection balancing / connection-pooling strategies:
- ✅ DNS resolution (partial support)
-
✅ Dialing (full support)
-
❌ Connection pooling policy (no support)
- ❌ Connection selection / retrieval policy (no support)
- ❌ External or shared pools specified by the code author (no support)
Givens:
- we can tell the client not to cache connections by setting the transport level DisableKeepAlives to true (note that this is different from the TCP level KeepAlives)
- When a connection is closed under these circumstances it's not clear if the close is being requested because the default pooling is disabled at the transport level vs some other protocol level issue, connection flaw has been detected, transaction has an unknown completion state, or transaction has been partially completed.
- Implementing a full transport and RoundTripper to fully implement a custom resolver strategy and round-robin pooling capability is non-trivial and likely to lead to significant issues and bugs.
- Significant complexity and overhead in deployment architecture is utilized to resolve these issues out of the program (using mesh frameworks, moving to gRPC, forcing the use of http2 by all clients, integrating provider-bespoke service-discovery libs managed by cloud providers)
Proposal:
Before a client would re-pool a connection internally or close it because the transport level DisableKeepAlives is true, check if the connection has implemented a Pooler
interface with one function PutToPool() bool
which when called if it exists circumvents the default pooling practice regardless of result and removes the need to call Close() on the connection when the result of the call is true.
This allows for the dialer to consistently act as the pool fetcher or connection instance creator and opens up the world of possibilities to authors to control both the LIFO vs FIFO behavior of the queue, testing connection state, and setting tcp level timeouts on releasing to the pool vs acquiring from the pool. Connection max-lifetimes can be easily implemented should people prefer it.
The usage possibilities unlocked is high, the complexity added is low, the reuse of trust/proven protocol practices established in the SDK is high.
It is an all around win-win-win.
Comment From: seankhliao
This is what the http.Roundtripper interface is for: to let you implement your own connection management. You don't have to delegate to http.Transport, and having it grow more knobs is not something we'd lightly do.
Comment From: gabyhelp
Related Issues
- net/http: implementing size capped connection pools #12289
- proposal: net/http: add .MaxConnLifespan or .SetMaxConnLifespan() to Transport #54429
- net/http: Transport: add a ConnectionManager interface to separate the connection management from http.Transport #22537
- proposal: net/http: expose statistics about the client connection pool #63494 (closed)
- x/net/http2: support graceful close of client connection #17292 (closed)
(Emoji vote if this was helpful or unhelpful; more detailed feedback welcome in this discussion.)