Fix sentinel error aliasing, hot-path allocations, and resource leaks

- Deduplicate sentinel errors: httpx.ErrNoHealthy, ErrCircuitOpen, and
  ErrRetryExhausted are now aliases to the canonical sub-package values
  so errors.Is works across package boundaries
- Retry transport returns ErrRetryExhausted only when all attempts are
  actually exhausted, not on early policy exit
- Balancer: pre-parse endpoint URLs at construction, replace req.Clone
  with cheap shallow struct copy to avoid per-request allocations
- Circuit breaker: Load before LoadOrStore to avoid allocating a Breaker
  on every request for known hosts
- Health checker: drain response body before close for connection reuse,
  probe endpoints concurrently, run initial probe synchronously in Start
- Client: add Close() to shut down health checker goroutine, propagate
  URL resolution errors instead of silently discarding them
- MockClock: fix lock ordering in Reset (clock.mu before t.mu), fix
  timer slice compaction to avoid backing-array aliasing, extract
  fireExpired to deduplicate Advance/Set
This commit is contained in:
2026-03-20 15:21:32 +03:00
parent f9a05f5c57
commit 5cfd1a7400
8 changed files with 155 additions and 48 deletions

View File

@@ -33,7 +33,8 @@ func TestTransport_PicksEndpointAndReplacesURL(t *testing.T) {
return okResponse(), nil
})
rt := Transport(endpoints)(base)
mw, _ := Transport(endpoints)
rt := mw(base)
req, err := http.NewRequest(http.MethodGet, "https://original.example.com/api/v1/users", nil)
if err != nil {
@@ -67,7 +68,8 @@ func TestTransport_ErrNoHealthyWhenNoEndpoints(t *testing.T) {
return nil, nil
})
rt := Transport(endpoints)(base)
mw, _ := Transport(endpoints)
rt := mw(base)
req, err := http.NewRequest(http.MethodGet, "https://example.com/test", nil)
if err != nil {