diff --git a/request_test.go b/request_test.go index 44bec45..2d1e5bf 100644 --- a/request_test.go +++ b/request_test.go @@ -12,11 +12,13 @@ import ( "io" "net" "net/http" + "net/http/httptest" "net/url" "os" "path/filepath" "strconv" "strings" + "sync" "testing" "time" @@ -2195,3 +2197,40 @@ func TestSetResultMustNotPanicOnNil(t *testing.T) { }() dc().R().SetResult(nil) } + +func TestRequestGH917(t *testing.T) { + // Mock server returns 500 status code to cause client retries. + srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { + b, err := io.ReadAll(r.Body) + assertError(t, err) + if len(b) > 0 { + // sometimes, the body is "testtest" instead of "test" + assertEqual(t, "test", string(b)) + } + w.WriteHeader(http.StatusInternalServerError) + })) + + client := New().AddRetryCondition( + func(r *Response, err error) bool { + return err != nil || r.StatusCode() > 499 + }, + ).SetRetryCount(3) + + wg := sync.WaitGroup{} + // Run tests concurrently to make the issue easily to observe. + for i := 0; i < 100; i++ { + wg.Add(1) + go func() { + defer wg.Done() + for j := 0; j < 10; j++ { + buf := bytes.NewBufferString("test") + // Trigger some retries + resp, err := client.R().SetBody(buf).SetContentLength(true).Execute(http.MethodPost, srv.URL) + assertNil(t, err) + assertEqual(t, http.StatusInternalServerError, resp.StatusCode()) + assertEqual(t, "", string(resp.Body())) + } + }() + } + wg.Wait() +} diff --git a/util.go b/util.go index e1a66ff..9f58a3f 100644 --- a/util.go +++ b/util.go @@ -286,12 +286,17 @@ func functionName(i interface{}) string { } func acquireBuffer() *bytes.Buffer { - return bufPool.Get().(*bytes.Buffer) + buf := bufPool.Get().(*bytes.Buffer) + if buf.Len() == 0 { + buf.Reset() + return buf + } + bufPool.Put(buf) + return new(bytes.Buffer) } func releaseBuffer(buf *bytes.Buffer) { if buf != nil { - buf.Reset() bufPool.Put(buf) } }