Skip to content

Commit 91b8a1a

Browse files
committed
Fix: Bulk processor retries indefinitely on failure
When all retries are exhausted the worker internal requests buffer needs to be cleared in failure scenarios. That is required because the commitFunc (and consequently the underlying BulkService.Do call) doesn't reset it when some error happens. Without clearing the internal buffer the worker will continue sending the same requests on the following rounds of execution. Kudos for this solution goes to @rwynn and @raiRaiyan . Resolves olivere#1278
1 parent 29ee989 commit 91b8a1a

File tree

2 files changed

+73
-0
lines changed

2 files changed

+73
-0
lines changed

bulk_processor.go

+8
Original file line numberDiff line numberDiff line change
@@ -580,6 +580,14 @@ func (w *bulkWorker) commit(ctx context.Context) error {
580580
err := RetryNotify(commitFunc, w.p.backoff, notifyFunc)
581581
w.updateStats(res)
582582
if err != nil {
583+
// After all retry attempts clear the requests for the next round. This is
584+
// important when backoff is disabled or limited to a number of rounds, and
585+
// the aftermath is a failure., because the commitFunc (and consequently the
586+
// underlying BulkService.Do call) will not clear the requests from the
587+
// worker. Without this the same requests will be used on the next round of
588+
// execution of the worker.
589+
w.service.Reset()
590+
583591
w.p.c.errorf("elastic: bulk processor %q failed: %v", w.p.name, err)
584592
}
585593

bulk_processor_test.go

+65
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,8 @@ import (
88
"context"
99
"fmt"
1010
"math/rand"
11+
"net/http"
12+
"net/http/httptest"
1113
"reflect"
1214
"sync/atomic"
1315
"testing"
@@ -335,6 +337,60 @@ func TestBulkProcessorFlush(t *testing.T) {
335337
}
336338
}
337339

340+
func TestBulkWorker_commit_clearFailedRequests(t *testing.T) {
341+
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {}))
342+
defer server.Close()
343+
344+
client, err := NewClient(
345+
SetURL(server.URL),
346+
SetSniff(false),
347+
SetHealthcheck(false),
348+
SetHttpClient(tooManyHTTPClient{}),
349+
)
350+
if err != nil {
351+
t.Fatal(err)
352+
}
353+
354+
var calls int
355+
356+
bulkProcessor, err := NewBulkProcessorService(client).
357+
BulkActions(-1).
358+
BulkSize(-1).
359+
FlushInterval(0).
360+
RetryItemStatusCodes().
361+
Backoff(StopBackoff{}).
362+
After(BulkAfterFunc(func(executionId int64, requests []BulkableRequest, response *BulkResponse, err error) {
363+
calls++
364+
if calls == 1 {
365+
if len(requests) != 10 {
366+
t.Errorf("expected 10 requests; got: %d", len(requests))
367+
}
368+
} else if len(requests) > 0 {
369+
t.Errorf("expected 0 requests; got: %d", len(requests))
370+
}
371+
})).Do(context.Background())
372+
373+
if err != nil {
374+
t.Fatal(err)
375+
}
376+
377+
for i := 0; i < 10; i++ {
378+
bulkProcessor.Add(NewBulkIndexRequest())
379+
}
380+
381+
// first flush should process 10 items
382+
if err := bulkProcessor.Flush(); err != nil {
383+
t.Fatal(err)
384+
}
385+
// second flush should process none (even if the first flush failed)
386+
if err := bulkProcessor.Flush(); err != nil {
387+
t.Fatal(err)
388+
}
389+
if err := bulkProcessor.Close(); err != nil {
390+
t.Fatal(err)
391+
}
392+
}
393+
338394
// -- Helper --
339395

340396
func testBulkProcessor(t *testing.T, numDocs int, svc *BulkProcessorService) {
@@ -427,3 +483,12 @@ func testBulkProcessor(t *testing.T, numDocs int, svc *BulkProcessorService) {
427483
t.Fatalf("expected %d documents; got: %d", numDocs, count)
428484
}
429485
}
486+
487+
type tooManyHTTPClient struct {
488+
}
489+
490+
func (t tooManyHTTPClient) Do(r *http.Request) (*http.Response, error) {
491+
recorder := httptest.NewRecorder()
492+
recorder.WriteHeader(http.StatusTooManyRequests)
493+
return recorder.Result(), nil
494+
}

0 commit comments

Comments
 (0)