Skip to content

Commit 2bedcd9

Browse files
committed
Add support for setting per-attempt-timeout
- At the moment, only "complete" requests can be retried (that result in an HTTP status).
1 parent f54fa8c commit 2bedcd9

File tree

3 files changed

+73
-0
lines changed

3 files changed

+73
-0
lines changed

internal/client/client.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -57,6 +57,9 @@ var DefaultAuth0ClientInfo = &Auth0ClientInfo{
5757
type RetryOptions struct {
5858
MaxRetries int
5959
Statuses []int
60+
61+
// PerAttemptTimeout can optionally be set to timeout individual API requests.
62+
PerAttemptTimeout time.Duration
6063
}
6164

6265
// IsEmpty checks whether the provided Auth0ClientInfo data is nil or has no data to allow
@@ -111,6 +114,7 @@ func RetriesTransport(base http.RoundTripper, r RetryOptions) http.RoundTripper
111114
),
112115
backoffDelay(),
113116
)
117+
tr.PerAttemptTimeout = r.PerAttemptTimeout
114118

115119
return tr
116120
}

management/management_option.go

Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ package management
33
import (
44
"context"
55
"net/http"
6+
"time"
67

78
"github.com/auth0/go-auth0/internal/client"
89
)
@@ -123,6 +124,22 @@ func WithRetries(maxRetries int, statuses []int) Option {
123124
}
124125
}
125126

127+
// RetryStrategy defines the retry rules that should be followed by the SDK when making requests.
128+
type RetryStrategy struct {
129+
MaxRetries int
130+
Statuses []int
131+
132+
// PerAttemptTimeout can optionally be set to timeout individual API requests.
133+
PerAttemptTimeout time.Duration
134+
}
135+
136+
// WithRetryStrategy configures the management client to only retry under the conditions provided.
137+
func WithRetryStrategy(retryStrategy RetryStrategy) Option {
138+
return func(m *Management) {
139+
m.retryStrategy = client.RetryOptions(retryStrategy)
140+
}
141+
}
142+
126143
// WithNoRetries configures the management client to only retry under the conditions provided.
127144
func WithNoRetries() Option {
128145
return func(m *Management) {

management/management_test.go

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ import (
1010
"net/http/httptest"
1111
"os"
1212
"runtime"
13+
"sync/atomic"
1314
"testing"
1415
"time"
1516

@@ -529,6 +530,57 @@ func TestRetries(t *testing.T) {
529530
assert.ErrorIs(t, err, context.Canceled)
530531
assert.Equal(t, 1, i) // 1 request should have been made before the context times out
531532
})
533+
534+
t.Run("Retry per request timeout", func(t *testing.T) {
535+
var i atomic.Int64
536+
ctx, cancel := context.WithCancel(context.Background())
537+
defer cancel()
538+
539+
h := http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
540+
ctx := r.Context()
541+
542+
c := i.Add(1)
543+
t.Log(c)
544+
if c == 2 {
545+
cancel()
546+
}
547+
548+
timer := time.NewTimer(10 * time.Second)
549+
select {
550+
case <-timer.C:
551+
t.Log("completed")
552+
w.WriteHeader(http.StatusOK)
553+
return
554+
case <-ctx.Done():
555+
t.Log("cancelled")
556+
w.WriteHeader(499)
557+
return
558+
}
559+
})
560+
561+
s := httptest.NewServer(h)
562+
defer s.Close()
563+
564+
m, err := New(
565+
s.URL,
566+
WithInsecure(),
567+
WithRetryStrategy(RetryStrategy{
568+
MaxRetries: 10,
569+
Statuses: []int{
570+
http.StatusInternalServerError,
571+
http.StatusBadGateway,
572+
http.StatusServiceUnavailable,
573+
http.StatusGatewayTimeout,
574+
},
575+
PerAttemptTimeout: 5 * time.Millisecond,
576+
}),
577+
)
578+
assert.NoError(t, err)
579+
580+
_, err = m.User.Read(ctx, "123")
581+
assert.ErrorIs(t, err, context.Canceled)
582+
assert.Equal(t, int64(2), i.Load()) // 1 request should have been made before the context times out
583+
})
532584
}
533585

534586
func TestApiCallContextTimeout(t *testing.T) {

0 commit comments

Comments
 (0)