diff --git a/backoff.go b/backoff.go new file mode 100644 index 0000000..2187cd5 --- /dev/null +++ b/backoff.go @@ -0,0 +1,47 @@ +package maps + +import ( + "math/rand" + "net/http" + "time" +) + +type backoff struct { + Transport *http.RoundTripper + MaxTries int + tries int + sleep func(time.Duration) +} + +func (bt *backoff) RoundTrip(req *http.Request) (*http.Response, error) { + if bt.Transport == nil { + bt.Transport = &http.DefaultTransport + } + if bt.MaxTries == 0 { + bt.MaxTries = 5 + } + if bt.sleep == nil { + bt.sleep = time.Sleep + } + wait := time.Second + var err error + for ; bt.tries < bt.MaxTries; bt.tries++ { + var resp *http.Response + resp, err = (*bt.Transport).RoundTrip(req) + if err != nil { + // fallthrough, retry + } else if resp.StatusCode >= http.StatusInternalServerError { + err = HTTPError{resp} + } else { + return resp, nil + } + if bt.tries == bt.MaxTries-1 { + // last try failed, just give up + continue + } + bt.sleep(wait) + jitter := time.Duration(rand.Intn(1000))*time.Millisecond - 500*time.Millisecond // jitter is +/- .5s + wait = wait*2 + jitter + } + return nil, err +} diff --git a/backoff_test.go b/backoff_test.go new file mode 100644 index 0000000..2dea33d --- /dev/null +++ b/backoff_test.go @@ -0,0 +1,35 @@ +package maps + +import ( + "net/http" + "net/http/httptest" + "testing" + "time" +) + +func TestBackoff(t *testing.T) { + s := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + http.Error(w, "fail", http.StatusInternalServerError) + })) + defer s.Close() + tries := 10 + sleeps := 0 + bt := &backoff{ + MaxTries: tries, + sleep: func(d time.Duration) { + sleeps += 1 + t.Logf("sleeping %s", d) + }, + } + c := &http.Client{Transport: bt} + r, _ := http.NewRequest("GET", s.URL, nil) + if _, err := c.Do(r); err == nil { + t.Errorf("expected error") + } + if sleeps != tries-1 { + t.Errorf("unexpected # of calls to sleep, got %d, want %d", tries-1, sleeps) + } + if bt.tries != tries { + t.Errorf("got %d tries, want %d", bt.tries, tries) + } +} diff --git a/maps.go b/maps.go index 8484847..4403233 100644 --- a/maps.go +++ b/maps.go @@ -23,7 +23,9 @@ func NewWorkClient(clientID, signature string) Client { } func (c Client) do(url string) (*http.Response, error) { - cl := &http.Client{Transport: c.Transport} + cl := &http.Client{Transport: &backoff{ + Transport: &c.Transport, + }} req, err := http.NewRequest("GET", url, nil) if err != nil { return nil, err