Skip to content

Commit

Permalink
Revert "Simplify API (#31)" (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
cristaloleg authored Aug 9, 2023
1 parent 14eed66 commit a52360e
Show file tree
Hide file tree
Showing 4 changed files with 58 additions and 28 deletions.
10 changes: 5 additions & 5 deletions examples_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ func ExampleClient() {
timeout := 10 * time.Millisecond
upto := 7
client := &http.Client{Timeout: time.Second}
hedged, _, err := hedgedhttp.NewClient(timeout, upto, client)
hedged, err := hedgedhttp.NewClient(timeout, upto, client)
if err != nil {
panic(err)
}
Expand All @@ -48,7 +48,7 @@ func ExampleRoundTripper() {
timeout := 10 * time.Millisecond
upto := 7
transport := http.DefaultTransport
hedged, stats, err := hedgedhttp.NewRoundTripper(timeout, upto, transport)
hedged, stats, err := hedgedhttp.NewRoundTripperAndStats(timeout, upto, transport)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -78,7 +78,7 @@ func Example_instrumented() {
Transport: http.DefaultTransport,
}

_, _, err := hedgedhttp.NewRoundTripper(time.Millisecond, 3, transport)
_, err := hedgedhttp.NewRoundTripper(time.Millisecond, 3, transport)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -109,7 +109,7 @@ func Example_ratelimited() {
Limiter: &RandomRateLimiter{},
}

_, _, err := hedgedhttp.NewRoundTripper(time.Millisecond, 3, transport)
_, err := hedgedhttp.NewRoundTripper(time.Millisecond, 3, transport)
if err != nil {
panic(err)
}
Expand Down Expand Up @@ -153,7 +153,7 @@ func ExampleMultiTransport() {
Hedged: &http.Transport{MaxIdleConns: 30}, // just an example
}

_, _, err := hedgedhttp.NewRoundTripper(time.Millisecond, 3, transport)
_, err := hedgedhttp.NewRoundTripper(time.Millisecond, 3, transport)
if err != nil {
panic(err)
}
Expand Down
46 changes: 38 additions & 8 deletions hedged.go
Original file line number Diff line number Diff line change
Expand Up @@ -13,36 +13,58 @@ import (
const infiniteTimeout = 30 * 24 * time.Hour // domain specific infinite

// NewClient returns a new http.Client which implements hedged requests pattern.
// Given Client starts a new request after a timeout from previous request.
// Starts no more than upto requests.
func NewClient(timeout time.Duration, upto int, client *http.Client) (*http.Client, error) {
newClient, _, err := NewClientAndStats(timeout, upto, client)
if err != nil {
return nil, err
}
return newClient, nil
}

// NewClientAndStats returns a new http.Client which implements hedged requests pattern
// And Stats object that can be queried to obtain client's metrics.
// Given Client starts a new request after a timeout from previous request.
// Starts no more than upto requests.
func NewClient(timeout time.Duration, upto int, client *http.Client) (*http.Client, *Stats, error) {
func NewClientAndStats(timeout time.Duration, upto int, client *http.Client) (*http.Client, *Stats, error) {
if client == nil {
client = &http.Client{
Timeout: 5 * time.Second,
}
}

newTransport, stats, err := NewRoundTripper(timeout, upto, client.Transport)
newTransport, metrics, err := NewRoundTripperAndStats(timeout, upto, client.Transport)
if err != nil {
return nil, nil, err
}

client.Transport = newTransport

return client, stats, nil
return client, metrics, nil
}

// NewRoundTripper returns a new http.RoundTripper which implements hedged requests pattern.
// Given RoundTripper starts a new request after a timeout from previous request.
// Starts no more than upto requests.
func NewRoundTripper(timeout time.Duration, upto int, rt http.RoundTripper) (http.RoundTripper, error) {
newRT, _, err := NewRoundTripperAndStats(timeout, upto, rt)
if err != nil {
return nil, err
}
return newRT, nil
}

// NewRoundTripperAndStats returns a new http.RoundTripper which implements hedged requests pattern
// And Stats object that can be queried to obtain client's metrics.
// Given RoundTripper starts a new request after a timeout from previous request.
// Starts no more than upto requests.
func NewRoundTripper(timeout time.Duration, upto int, rt http.RoundTripper) (http.RoundTripper, *Stats, error) {
func NewRoundTripperAndStats(timeout time.Duration, upto int, rt http.RoundTripper) (http.RoundTripper, *Stats, error) {
switch {
case timeout < 0:
return nil, nil, errors.New("timeout cannot be negative")
return nil, nil, errors.New("hedgedhttp: timeout cannot be negative")
case upto < 1:
return nil, nil, errors.New("upto must be greater than 0")
return nil, nil, errors.New("hedgedhttp: upto must be greater than 0")
}

if rt == nil {
Expand Down Expand Up @@ -211,11 +233,16 @@ func runInPool(task func()) {
// accumulate errors in cases and return them as a single "error".
// Inspired by https://github.com/hashicorp/go-multierror
type MultiError struct {
Errors []error
Errors []error
ErrorFormatFn ErrorFormatFunc
}

func (e *MultiError) Error() string {
return listFormatFunc(e.Errors)
fn := e.ErrorFormatFn
if fn == nil {
fn = listFormatFunc
}
return fn(e.Errors)
}

func (e *MultiError) String() string {
Expand All @@ -232,6 +259,9 @@ func (e *MultiError) ErrorOrNil() error {
}
}

// ErrorFormatFunc is called by MultiError to return the list of errors as a string.
type ErrorFormatFunc func([]error) string

func listFormatFunc(es []error) string {
if len(es) == 1 {
return fmt.Sprintf("1 error occurred:\n\t* %s\n\n", es[0])
Expand Down
2 changes: 1 addition & 1 deletion hedged_bench_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,7 +67,7 @@ func BenchmarkHedgedRequest(b *testing.B) {
errors := uint64(0)
var snapshot atomic.Value

hedgedTarget, metrics, err := hedgedhttp.NewRoundTripper(10*time.Nanosecond, 10, target)
hedgedTarget, metrics, err := hedgedhttp.NewRoundTripperAndStats(10*time.Nanosecond, 10, target)
if err != nil {
b.Fatalf("want nil, got %s", err)
}
Expand Down
28 changes: 14 additions & 14 deletions hedged_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,22 +17,22 @@ import (
)

func TestValidateInput(t *testing.T) {
_, _, err := hedgedhttp.NewClient(-time.Second, 0, nil)
_, _, err := hedgedhttp.NewClientAndStats(-time.Second, 0, nil)
if err == nil {
t.Fatalf("want err, got nil")
}

_, _, err = hedgedhttp.NewClient(time.Second, -1, nil)
_, _, err = hedgedhttp.NewClientAndStats(time.Second, -1, nil)
if err == nil {
t.Fatalf("want err, got nil")
}

_, _, err = hedgedhttp.NewClient(time.Second, 0, nil)
_, _, err = hedgedhttp.NewClientAndStats(time.Second, 0, nil)
if err == nil {
t.Fatalf("want err, got nil")
}

_, _, err = hedgedhttp.NewRoundTripper(time.Second, 0, nil)
_, err = hedgedhttp.NewRoundTripper(time.Second, 0, nil)
if err == nil {
t.Fatalf("want err, got nil")
}
Expand All @@ -52,7 +52,7 @@ func TestUpto(t *testing.T) {
}

const upto = 7
client, _, err := hedgedhttp.NewClient(10*time.Millisecond, upto, nil)
client, err := hedgedhttp.NewClient(10*time.Millisecond, upto, nil)
if err != nil {
t.Fatal(err)
}
Expand Down Expand Up @@ -82,7 +82,7 @@ func TestUptoWithInstrumentation(t *testing.T) {
}

const upto = 7
client, metrics, err := hedgedhttp.NewClient(10*time.Millisecond, upto, nil)
client, metrics, err := hedgedhttp.NewClientAndStats(10*time.Millisecond, upto, nil)
if err != nil {
t.Fatalf("want nil, got %s", err)
}
Expand Down Expand Up @@ -131,7 +131,7 @@ func TestNoTimeout(t *testing.T) {

const upto = 10

client, metrics, err := hedgedhttp.NewClient(0, upto, nil)
client, metrics, err := hedgedhttp.NewClientAndStats(0, upto, nil)
if err != nil {
t.Fatalf("want nil, got %s", err)
}
Expand Down Expand Up @@ -174,7 +174,7 @@ func TestFirstIsOK(t *testing.T) {
t.Fatal(err)
}

client, metrics, err := hedgedhttp.NewClient(10*time.Millisecond, 10, nil)
client, metrics, err := hedgedhttp.NewClientAndStats(10*time.Millisecond, 10, nil)
if err != nil {
t.Fatalf("want nil, got %s", err)
}
Expand Down Expand Up @@ -223,7 +223,7 @@ func TestBestResponse(t *testing.T) {
if err != nil {
t.Fatal(err)
}
client, metrics, err := hedgedhttp.NewClient(10*time.Millisecond, 5, nil)
client, metrics, err := hedgedhttp.NewClientAndStats(10*time.Millisecond, 5, nil)
if err != nil {
t.Fatalf("want nil, got %s", err)
}
Expand Down Expand Up @@ -284,7 +284,7 @@ func TestGetSuccessEvenWithErrorsPresent(t *testing.T) {
t.Fatal(err)
}

client, metrics, err := hedgedhttp.NewClient(10*time.Millisecond, int(upto), nil)
client, metrics, err := hedgedhttp.NewClientAndStats(10*time.Millisecond, int(upto), nil)
if err != nil {
t.Fatalf("want nil, got %s", err)
}
Expand Down Expand Up @@ -341,7 +341,7 @@ func TestGetFailureAfterAllRetries(t *testing.T) {
t.Fatal(err)
}

client, metrics, err := hedgedhttp.NewClient(time.Millisecond, upto, nil)
client, metrics, err := hedgedhttp.NewClientAndStats(time.Millisecond, upto, nil)
if err != nil {
t.Fatalf("want nil, got %s", err)
}
Expand Down Expand Up @@ -396,7 +396,7 @@ func TestHangAllExceptLast(t *testing.T) {
t.Fatal(err)
}

client, metrics, err := hedgedhttp.NewClient(10*time.Millisecond, upto, nil)
client, metrics, err := hedgedhttp.NewClientAndStats(10*time.Millisecond, upto, nil)
if err != nil {
t.Fatalf("want nil, got %s", err)
}
Expand Down Expand Up @@ -448,7 +448,7 @@ func TestCancelByClient(t *testing.T) {
}

upto := 5
client, metrics, err := hedgedhttp.NewClient(10*time.Millisecond, upto, nil)
client, metrics, err := hedgedhttp.NewClientAndStats(10*time.Millisecond, upto, nil)
if err != nil {
t.Fatalf("want nil, got %s", err)
}
Expand Down Expand Up @@ -502,7 +502,7 @@ func TestIsHedged(t *testing.T) {
}

const upto = 7
client, _, err := hedgedhttp.NewRoundTripper(10*time.Millisecond, upto, rt)
client, err := hedgedhttp.NewRoundTripper(10*time.Millisecond, upto, rt)
if err != nil {
t.Fatal(err)
}
Expand Down

0 comments on commit a52360e

Please sign in to comment.