Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix data race in hystrix client #67

Closed
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 5 additions & 2 deletions hystrix/hystrix_client.go
Original file line number Diff line number Diff line change
Expand Up @@ -179,7 +179,7 @@ func (hhc *Client) Do(request *http.Request) (*http.Response, error) {
}

err = hystrix.Do(hhc.hystrixCommandName, func() error {
response, err = hhc.client.Do(request)
resp, err := hhc.client.Do(request)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Can you elaborate on this fix plz ?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

There are two possible source of errors, hystrix and net/http. If both having the same timeout value, the err will be in race, as it can override one another.

if bodyReader != nil {
// Reset the body reader after the request since at this point it's already read
// Note that it's safe to ignore the error here since the 0,0 position is always valid
Expand All @@ -190,9 +190,12 @@ func (hhc *Client) Do(request *http.Request) (*http.Response, error) {
return err
}

if response.StatusCode >= http.StatusInternalServerError {
response = resp

if resp.StatusCode >= http.StatusInternalServerError {
return err5xx
}

return nil
}, hhc.fallbackFunc)

Expand Down
83 changes: 51 additions & 32 deletions hystrix/hystrix_client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import (
"io/ioutil"
"net/http"
"net/http/httptest"
"strings"
"sync"
"testing"
"time"

"strings"

"github.com/gojektech/heimdall"
"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
Expand All @@ -27,7 +27,7 @@ func (c *myHTTPClient) Do(request *http.Request) (*http.Response, error) {
func TestHystrixHTTPClientDoSuccess(t *testing.T) {
client := NewClient(
WithHTTPTimeout(50*time.Millisecond),
WithCommandName("some_command_name"),
WithCommandName("TestHystrixHTTPClientDoSuccess"),
WithHystrixTimeout(10*time.Millisecond),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand Down Expand Up @@ -64,7 +64,7 @@ func TestHystrixHTTPClientDoSuccess(t *testing.T) {
func TestHystrixHTTPClientGetSuccess(t *testing.T) {
client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("some_command_name"),
WithCommandName("TestHystrixHTTPClientGetSuccess"),
WithHystrixTimeout(10),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand Down Expand Up @@ -98,7 +98,7 @@ func TestHystrixHTTPClientGetSuccess(t *testing.T) {
func TestHystrixHTTPClientPostSuccess(t *testing.T) {
client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("some_command_name"),
WithCommandName("TestHystrixHTTPClientPostSuccess"),
WithHystrixTimeout(10),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand Down Expand Up @@ -141,7 +141,7 @@ func TestHystrixHTTPClientPostSuccess(t *testing.T) {
func TestHystrixHTTPClientDeleteSuccess(t *testing.T) {
client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("some_command_name"),
WithCommandName("TestHystrixHTTPClientDeleteSuccess"),
WithHystrixTimeout(10),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand Down Expand Up @@ -175,7 +175,7 @@ func TestHystrixHTTPClientDeleteSuccess(t *testing.T) {
func TestHystrixHTTPClientPutSuccess(t *testing.T) {
client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("some_command_name"),
WithCommandName("TestHystrixHTTPClientPutSuccess"),
WithHystrixTimeout(10),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand Down Expand Up @@ -218,7 +218,7 @@ func TestHystrixHTTPClientPutSuccess(t *testing.T) {
func TestHystrixHTTPClientPatchSuccess(t *testing.T) {
client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("some_command_name"),
WithCommandName("TestHystrixHTTPClientPatchSuccess"),
WithHystrixTimeout(10),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand Down Expand Up @@ -264,7 +264,7 @@ func TestHystrixHTTPClientRetriesGetOnFailure(t *testing.T) {

client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("some_command_name"),
WithCommandName("TestHystrixHTTPClientRetriesGetOnFailure"),
WithHystrixTimeout(10),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand All @@ -287,7 +287,7 @@ func TestHystrixHTTPClientRetriesGetOnFailure5xx(t *testing.T) {

client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("some_command_name_5xx"),
WithCommandName("TestHystrixHTTPClientRetriesGetOnFailure5xx"),
WithHystrixTimeout(10),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand Down Expand Up @@ -321,7 +321,7 @@ func BenchmarkHystrixHTTPClientRetriesGetOnFailure(b *testing.B) {

client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("some_command_name"),
WithCommandName("BenchmarkHystrixHTTPClientRetriesGetOnFailure"),
WithHystrixTimeout(10),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand Down Expand Up @@ -351,7 +351,7 @@ func TestHystrixHTTPClientRetriesPostOnFailure(t *testing.T) {

client := NewClient(
WithHTTPTimeout(50*time.Millisecond),
WithCommandName("some_command_name"),
WithCommandName("TestHystrixHTTPClientRetriesPostOnFailure"),
WithHystrixTimeout(10*time.Millisecond),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand Down Expand Up @@ -384,7 +384,7 @@ func BenchmarkHystrixHTTPClientRetriesPostOnFailure(b *testing.B) {

client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("some_command_name"),
WithCommandName("BenchmarkHystrixHTTPClientRetriesPostOnFailure"),
WithHystrixTimeout(10),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand All @@ -407,25 +407,10 @@ func BenchmarkHystrixHTTPClientRetriesPostOnFailure(b *testing.B) {
}
}

func TestHystrixHTTPClientReturnsFallbackFailureWithoutFallBackFunction(t *testing.T) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why did you deleted this test?

client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("some_command_name"),
WithHystrixTimeout(10),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
WithSleepWindow(100),
WithRequestVolumeThreshold(10),
)

_, err := client.Get("http://foobar.example", http.Header{})
assert.Equal(t, err.Error(), "hystrix: circuit open")
}

func TestHystrixHTTPClientReturnsFallbackFailureWithAFallBackFunctionWhichReturnAnError(t *testing.T) {
client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("some_command_name"),
WithCommandName("TestHystrixHTTPClientReturnsFallbackFailureWithAFallBackFunctionWhichReturnAnError"),
WithHystrixTimeout(10),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand All @@ -448,7 +433,7 @@ func TestFallBackFunctionIsCalledWithHystrixHTTPClient(t *testing.T) {

client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("some_command_name"),
WithCommandName("TestFallBackFunctionIsCalledWithHystrixHTTPClient"),
WithHystrixTimeout(10),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand All @@ -469,7 +454,7 @@ func TestFallBackFunctionIsCalledWithHystrixHTTPClient(t *testing.T) {
func TestHystrixHTTPClientReturnsFallbackFailureWithAFallBackFunctionWhichReturnsNil(t *testing.T) {
client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("some_command_name"),
WithCommandName("TestHystrixHTTPClientReturnsFallbackFailureWithAFallBackFunctionWhichReturnsNil"),
WithHystrixTimeout(10),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand All @@ -488,7 +473,7 @@ func TestHystrixHTTPClientReturnsFallbackFailureWithAFallBackFunctionWhichReturn
func TestCustomHystrixHTTPClientDoSuccess(t *testing.T) {
client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("some_new_command_name"),
WithCommandName("TestCustomHystrixHTTPClientDoSuccess"),
WithHystrixTimeout(10),
WithMaxConcurrentRequests(100),
WithErrorPercentThreshold(10),
Expand Down Expand Up @@ -518,6 +503,40 @@ func TestCustomHystrixHTTPClientDoSuccess(t *testing.T) {
assert.Equal(t, "{ \"response\": \"ok\" }", string(body))
}

func TestHystrixHTTPClientGetReturnedURLTimeout(t *testing.T) {

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have tested this branch and the tests pass successfully

t.Parallel()

client := NewClient(
WithHTTPTimeout(10*time.Millisecond),
WithCommandName("TestHystrixHTTPClientGetReturnedURLTimeout"),
WithHystrixTimeout(10*time.Millisecond),
WithMaxConcurrentRequests(1000000),
WithErrorPercentThreshold(100),
WithSleepWindow(1),
WithRequestVolumeThreshold(1000000),
)

dummyHandler := func(w http.ResponseWriter, r *http.Request) {
time.Sleep(1 * time.Second)
}

server := httptest.NewServer(http.HandlerFunc(dummyHandler))
defer server.Close()

wg := sync.WaitGroup{}

for n := 0; n < 1000; n++ {
wg.Add(1)
go func() {
defer wg.Done()

_, err := client.Get(server.URL, http.Header{})
assert.Error(t, err)
}()
}
wg.Wait()
}

func respBody(t *testing.T, response *http.Response) string {
if response.Body != nil {
defer func() {
Expand Down