@@ -34,19 +34,17 @@ import (
3434// preserving path and query.
3535type FailoverRoundTripper struct {
3636 cfg * Configuration
37- opts * FailoverOptions
3837 base http.RoundTripper
3938}
4039
4140// NewFailoverRoundTripper creates a new FailoverRoundTripper.
4241// If opts is nil, it will fall back to cfg.Failover.
43- func NewFailoverRoundTripper (cfg * Configuration , opts * FailoverOptions , base http.RoundTripper ) http.RoundTripper {
42+ func NewFailoverRoundTripper (cfg * Configuration , base http.RoundTripper ) http.RoundTripper {
4443 if base == nil {
4544 base = http .DefaultTransport
4645 }
4746 return & FailoverRoundTripper {
4847 cfg : cfg ,
49- opts : opts ,
5048 base : base ,
5149 }
5250}
@@ -68,14 +66,13 @@ func (t *FailoverRoundTripper) RoundTrip(req *http.Request) (*http.Response, err
6866 return t .base .RoundTrip (req )
6967 }
7068
71- fo := t .opts
72- if fo == nil {
73- fo = t .cfg .Failover
74- }
69+ fo := t .cfg .Failover
7570 if fo == nil {
7671 return t .base .RoundTrip (req )
7772 }
7873
74+ bo := fo .ExponentialBackoff .NewExponentialBackoff ()
75+
7976 servers := t .cfg .Servers
8077 order := serverOrderFor (fo .Strategy , len (servers ))
8178 if order == nil {
@@ -88,8 +85,9 @@ func (t *FailoverRoundTripper) RoundTrip(req *http.Request) (*http.Response, err
8885 return t .base .RoundTrip (req )
8986 }
9087
88+ maxRetries := t .cfg .MaxRetries
9189 var lastErr error
92- for attempt := range len ( servers ) {
90+ for attempt := range maxRetries {
9391 serverURL := servers [order (attempt )].URL
9492
9593 targetURL , err := url .Parse (serverURL )
@@ -111,32 +109,33 @@ func (t *FailoverRoundTripper) RoundTrip(req *http.Request) (*http.Response, err
111109 }
112110
113111 resp , err := t .base .RoundTrip (attemptReq )
114- if err == nil {
115- if shouldFailoverOnStatus (fo , resp .StatusCode ) {
116- if SdkLogLevel .Satisfies (Debug ) {
117- SdkLogger .Printf ("[Failover] status=%d triggers failover to next server" , resp .StatusCode )
118- }
119- // Drain/close body to allow connection reuse.
120- if resp .Body != nil {
121- _ , _ = io .Copy (io .Discard , resp .Body )
122- _ = resp .Body .Close ()
123- }
124- lastErr = fmt .Errorf ("failover status: %s" , resp .Status )
125- continue
112+ if err != nil {
113+ lastErr = err
114+ retryable := isNetworkErrorRT (attemptReq .Context (), err , fo .RetryOnTimeout )
115+ if ! retryable {
116+ return nil , err
126117 }
127- return resp , nil
118+ if SdkLogLevel .Satisfies (Debug ) {
119+ SdkLogger .Printf ("[Failover] network error: %v; trying next server" , err )
120+ }
121+
122+ backoff (attemptReq .Context (), bo .NextBackOff ())
123+ continue
128124 }
129125
130- lastErr = err
131- retryable := isNetworkErrorRT (attemptReq .Context (), err , fo .RetryOnTimeout )
132- if ! retryable {
133- return nil , err
126+ if ! shouldFailoverOnStatus (fo , resp .StatusCode ) {
127+ return resp , nil
134128 }
129+
135130 if SdkLogLevel .Satisfies (Debug ) {
136- SdkLogger .Printf ("[Failover] network error: %v; trying next server" , err )
131+ SdkLogger .Printf ("[Failover] status=%d triggers failover to next server" , resp . StatusCode )
137132 }
138-
139- tinyBackoff (attemptReq .Context ())
133+ // Drain/close body to allow connection reuse.
134+ if resp .Body != nil {
135+ _ , _ = io .Copy (io .Discard , resp .Body )
136+ _ = resp .Body .Close ()
137+ }
138+ lastErr = fmt .Errorf ("failover status: %s" , resp .Status )
140139 }
141140
142141 return nil , lastErr
@@ -184,10 +183,6 @@ var defaultRetryableMethods = map[string]bool{
184183}
185184
186185func isNetworkErrorRT (ctx context.Context , err error , retryOnTimeout bool ) bool {
187- if err == nil {
188- return false
189- }
190-
191186 // 1. Check for standard DNS resolution errors (typed).
192187 var dnsErr * net.DNSError
193188 if errors .As (err , & dnsErr ) {
@@ -225,10 +220,7 @@ func isNetworkErrorRT(ctx context.Context, err error, retryOnTimeout bool) bool
225220 return false
226221}
227222
228- // todo replace with a more robust backoff strategy exponential with jitter
229- func tinyBackoff (ctx context.Context ) {
230- // 10ms is enough to avoid busy-looping while staying responsive.
231- t := 10 * time .Millisecond
223+ func backoff (ctx context.Context , t time.Duration ) {
232224 if ctx == nil {
233225 time .Sleep (t )
234226 return
0 commit comments