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:
@@ -1,6 +1,8 @@
|
||||
package retry
|
||||
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
@@ -8,6 +10,10 @@ import (
|
||||
"git.codelab.vc/pkg/httpx/middleware"
|
||||
)
|
||||
|
||||
// ErrRetryExhausted is returned when all retry attempts have been exhausted
|
||||
// and the last attempt also failed.
|
||||
var ErrRetryExhausted = errors.New("httpx: all retry attempts exhausted")
|
||||
|
||||
// Policy decides whether a failed request should be retried.
|
||||
type Policy interface {
|
||||
// ShouldRetry reports whether the request should be retried. The extra
|
||||
@@ -28,6 +34,7 @@ func Transport(opts ...Option) middleware.Middleware {
|
||||
return middleware.RoundTripperFunc(func(req *http.Request) (*http.Response, error) {
|
||||
var resp *http.Response
|
||||
var err error
|
||||
var exhausted bool
|
||||
|
||||
for attempt := range cfg.maxAttempts {
|
||||
// For retries (attempt > 0), restore the request body.
|
||||
@@ -48,6 +55,7 @@ func Transport(opts ...Option) middleware.Middleware {
|
||||
|
||||
// Last attempt — return whatever we got.
|
||||
if attempt == cfg.maxAttempts-1 {
|
||||
exhausted = true
|
||||
break
|
||||
}
|
||||
|
||||
@@ -85,6 +93,10 @@ func Transport(opts ...Option) middleware.Middleware {
|
||||
}
|
||||
}
|
||||
|
||||
// Wrap with ErrRetryExhausted only when all attempts were used.
|
||||
if exhausted && err != nil {
|
||||
err = fmt.Errorf("%w: %w", ErrRetryExhausted, err)
|
||||
}
|
||||
return resp, err
|
||||
})
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user