Skip to content

Commit

Permalink
feat: improve retry behavior on context deadline
Browse files Browse the repository at this point in the history
  • Loading branch information
stainless-bot committed Sep 25, 2023
1 parent 68a64bd commit 4ecce9f
Show file tree
Hide file tree
Showing 2 changed files with 41 additions and 6 deletions.
42 changes: 39 additions & 3 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,7 @@ import (
"github.com/lithic-com/lithic-go/option"
)

func TestCancel(t *testing.T) {
func TestContextCancel(t *testing.T) {
client := lithic.NewClient(
option.WithBaseURL("http://127.0.0.1:4010"),
option.WithAPIKey("APIKey"),
Expand All @@ -28,14 +28,15 @@ func TestCancel(t *testing.T) {
}
}

// neverTransport never completes a request and waits for the Context to be done.
type neverTransport struct{}

func (t *neverTransport) RoundTrip(req *http.Request) (*http.Response, error) {
<-req.Context().Done()
return nil, fmt.Errorf("cancelled")
}

func TestCancelDelay(t *testing.T) {
func TestContextCancelDelay(t *testing.T) {
client := lithic.NewClient(
option.WithBaseURL("http://127.0.0.1:4010"),
option.WithAPIKey("APIKey"),
Expand All @@ -50,6 +51,41 @@ func TestCancelDelay(t *testing.T) {
Type: lithic.F(lithic.CardNewParamsTypeSingleUse),
})
if err == nil || res != nil {
t.Error("Expected there to be a cancel error and for the response to be nil")
t.Error("expected there to be a cancel error and for the response to be nil")
}
}

func TestContextDeadline(t *testing.T) {
testTimeout := time.After(3 * time.Second)
testDone := make(chan bool)

deadline := time.Now().Add(100 * time.Millisecond)
deadlineCtx, cancel := context.WithDeadline(context.Background(), deadline)
defer cancel()

go func() {
client := lithic.NewClient(
option.WithBaseURL("http://127.0.0.1:4010"),
option.WithAPIKey("APIKey"),
option.WithHTTPClient(&http.Client{Transport: &neverTransport{}}),
)
res, err := client.Cards.New(deadlineCtx, lithic.CardNewParams{
Type: lithic.F(lithic.CardNewParamsTypeSingleUse),
})
if err == nil || res != nil {
t.Error("expected there to be a deadline error and for the response to be nil")
}
testDone <- true
}()

select {
case <-testTimeout:
t.Fatal("client didn't finish in time")
case <-testDone:
diff := time.Now().Sub(deadline)
if diff < -20*time.Millisecond || 20*time.Millisecond < diff {
t.Logf("error difference: %v", diff)
t.Fatal("client did not return within 20ms of context deadline")
}
}
}
5 changes: 2 additions & 3 deletions internal/requestconfig/requestconfig.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,6 @@ import (
"bytes"
"context"
"encoding/json"
"errors"
"fmt"
"io"
"math"
Expand Down Expand Up @@ -228,8 +227,8 @@ func (cfg *RequestConfig) Execute() (err error) {
}

res, err = handler(cfg.Request.Clone(ctx))
if errors.Is(err, context.Canceled) {
return err
if ctx != nil && ctx.Err() != nil {
return ctx.Err()
}
if !shouldRetry(cfg.Request, res) || retryCount >= cfg.MaxRetries {
break
Expand Down

0 comments on commit 4ecce9f

Please sign in to comment.