Stop the retry backoff from outliving its deadline
A fetch runs under a 30-second per-source budget, but the retry transport slept without reference to it, so backoff could convert a reportable provider status into an opaque "context deadline exceeded". Three changes. The final attempt no longer sleeps: nothing follows it, so the wait only spent the caller's deadline to return a response already in hand — up to a second per failed request, across sixteen drivers. Retry-After is now honoured, in both the delta-seconds and HTTP-date forms; ignoring it meant retrying a 429 after 250ms and earning another 429, spending the whole retry budget in under a second. And a wait is skipped entirely when it exceeds the remaining deadline or a 5s cap, because a retry that lands after the deadline cannot succeed — the throttled response is surfaced instead so the caller reports what the provider actually said. The type moves from google_workspace.go to driver.go, which is where the other shared driver machinery lives; sixteen drivers construct it and none of them are Google Workspace. It had no tests, so it has them now. Signed-off-by: Aurélien Sibiril <81782+aureliensibiril@users.noreply.github.com>
This commit is contained in:
@@ -21,10 +21,8 @@
|
||||
package drivers
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"context"
|
||||
"fmt"
|
||||
"io"
|
||||
"net/http"
|
||||
"time"
|
||||
|
||||
@@ -50,49 +48,6 @@ func NewGoogleWorkspaceDriver(httpClient *http.Client) *GoogleWorkspaceDriver {
|
||||
}
|
||||
}
|
||||
|
||||
// retryRoundTripper retries requests that receive 5xx or 429 responses
|
||||
// with exponential backoff.
|
||||
type retryRoundTripper struct {
|
||||
next http.RoundTripper
|
||||
maxRetries int
|
||||
}
|
||||
|
||||
func (rt *retryRoundTripper) RoundTrip(req *http.Request) (*http.Response, error) {
|
||||
transport := rt.next
|
||||
if transport == nil {
|
||||
transport = http.DefaultTransport
|
||||
}
|
||||
|
||||
var lastResp *http.Response
|
||||
|
||||
for attempt := range rt.maxRetries {
|
||||
resp, err := transport.RoundTrip(req)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if resp.StatusCode != http.StatusTooManyRequests && resp.StatusCode < 500 {
|
||||
return resp, nil
|
||||
}
|
||||
|
||||
// Buffer and re-attach the body so the caller can still read it
|
||||
// if this turns out to be the final (retry-exhausted) response.
|
||||
body, _ := io.ReadAll(resp.Body)
|
||||
_ = resp.Body.Close()
|
||||
resp.Body = io.NopCloser(bytes.NewReader(body))
|
||||
lastResp = resp
|
||||
|
||||
backoff := time.Duration(250*(1<<attempt)) * time.Millisecond
|
||||
select {
|
||||
case <-req.Context().Done():
|
||||
return nil, req.Context().Err()
|
||||
case <-time.After(backoff):
|
||||
}
|
||||
}
|
||||
|
||||
return lastResp, nil
|
||||
}
|
||||
|
||||
func (d *GoogleWorkspaceDriver) ListAccounts(ctx context.Context) ([]AccountRecord, error) {
|
||||
adminService, err := admin.NewService(ctx, option.WithHTTPClient(d.httpClient))
|
||||
if err != nil {
|
||||
|
||||
Reference in New Issue
Block a user