github.com/Jeffail/benthos/v3@v3.65.0/public/service/input_auto_retry_batched_test.go (about)

     1  package service
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"sync"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/Jeffail/benthos/v3/lib/types"
    11  	"github.com/stretchr/testify/assert"
    12  	"github.com/stretchr/testify/require"
    13  )
    14  
    15  type mockBatchInput struct {
    16  	msgsToSnd []MessageBatch
    17  	ackRcvd   []error
    18  
    19  	connChan  chan error
    20  	readChan  chan error
    21  	ackChan   chan error
    22  	closeChan chan error
    23  }
    24  
    25  func newMockBatchInput() *mockBatchInput {
    26  	return &mockBatchInput{
    27  		connChan:  make(chan error),
    28  		readChan:  make(chan error),
    29  		ackChan:   make(chan error),
    30  		closeChan: make(chan error),
    31  	}
    32  }
    33  
    34  func (i *mockBatchInput) Connect(ctx context.Context) error {
    35  	cerr, open := <-i.connChan
    36  	if !open {
    37  		return types.ErrNotConnected
    38  	}
    39  	return cerr
    40  }
    41  
    42  func (i *mockBatchInput) ReadBatch(ctx context.Context) (MessageBatch, AckFunc, error) {
    43  	select {
    44  	case <-ctx.Done():
    45  		return nil, nil, types.ErrTimeout
    46  	case err, open := <-i.readChan:
    47  		if !open {
    48  			return nil, nil, types.ErrNotConnected
    49  		}
    50  		if err != nil {
    51  			return nil, nil, err
    52  		}
    53  	}
    54  	i.ackRcvd = append(i.ackRcvd, errors.New("ack not received"))
    55  	index := len(i.ackRcvd) - 1
    56  
    57  	nextBatch := MessageBatch{}
    58  	if len(i.msgsToSnd) > 0 {
    59  		nextBatch = i.msgsToSnd[0]
    60  		i.msgsToSnd = i.msgsToSnd[1:]
    61  	}
    62  
    63  	return nextBatch.Copy(), func(ctx context.Context, res error) error {
    64  		i.ackRcvd[index] = res
    65  		return <-i.ackChan
    66  	}, nil
    67  }
    68  
    69  func (i *mockBatchInput) Close(ctx context.Context) error {
    70  	return <-i.closeChan
    71  }
    72  
    73  func TestBatchAutoRetryClose(t *testing.T) {
    74  	t.Parallel()
    75  
    76  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
    77  	defer cancel()
    78  
    79  	readerImpl := newMockBatchInput()
    80  	pres := AutoRetryNacksBatched(readerImpl)
    81  
    82  	expErr := errors.New("foo error")
    83  
    84  	wg := sync.WaitGroup{}
    85  	wg.Add(1)
    86  
    87  	go func() {
    88  		defer wg.Done()
    89  
    90  		err := pres.Connect(ctx)
    91  		require.NoError(t, err)
    92  
    93  		assert.Equal(t, expErr, pres.Close(ctx))
    94  	}()
    95  
    96  	select {
    97  	case readerImpl.connChan <- nil:
    98  	case <-time.After(time.Second):
    99  		t.Error("Timed out")
   100  	}
   101  
   102  	select {
   103  	case readerImpl.closeChan <- expErr:
   104  	case <-time.After(time.Second):
   105  		t.Error("Timed out")
   106  	}
   107  
   108  	wg.Wait()
   109  }
   110  
   111  func TestBatchAutoRetryHappy(t *testing.T) {
   112  	t.Parallel()
   113  
   114  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
   115  	defer cancel()
   116  
   117  	readerImpl := newMockBatchInput()
   118  	readerImpl.msgsToSnd = append(readerImpl.msgsToSnd, MessageBatch{
   119  		NewMessage([]byte("foo")),
   120  		NewMessage([]byte("bar")),
   121  	})
   122  
   123  	pres := AutoRetryNacksBatched(readerImpl)
   124  
   125  	go func() {
   126  		select {
   127  		case readerImpl.connChan <- nil:
   128  		case <-time.After(time.Second):
   129  			t.Error("Timed out")
   130  		}
   131  		select {
   132  		case readerImpl.readChan <- nil:
   133  		case <-time.After(time.Second):
   134  			t.Error("Timed out")
   135  		}
   136  	}()
   137  
   138  	require.NoError(t, pres.Connect(ctx))
   139  
   140  	batch, _, err := pres.ReadBatch(ctx)
   141  	require.NoError(t, err)
   142  	require.Len(t, batch, 2)
   143  
   144  	act, err := batch[0].AsBytes()
   145  	require.NoError(t, err)
   146  	assert.Equal(t, "foo", string(act))
   147  
   148  	act, err = batch[1].AsBytes()
   149  	require.NoError(t, err)
   150  	assert.Equal(t, "bar", string(act))
   151  }
   152  
   153  func TestBatchAutoRetryErrorProp(t *testing.T) {
   154  	t.Parallel()
   155  
   156  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
   157  	defer cancel()
   158  
   159  	readerImpl := newMockBatchInput()
   160  	pres := AutoRetryNacksBatched(readerImpl)
   161  
   162  	expErr := errors.New("foo")
   163  
   164  	go func() {
   165  		select {
   166  		case readerImpl.connChan <- expErr:
   167  		case <-time.After(time.Second):
   168  			t.Error("Timed out")
   169  		}
   170  		select {
   171  		case readerImpl.readChan <- expErr:
   172  		case <-time.After(time.Second):
   173  			t.Error("Timed out")
   174  		}
   175  		select {
   176  		case readerImpl.readChan <- nil:
   177  		case <-time.After(time.Second):
   178  			t.Error("Timed out")
   179  		}
   180  		select {
   181  		case readerImpl.ackChan <- expErr:
   182  		case <-time.After(time.Second):
   183  			t.Error("Timed out")
   184  		}
   185  	}()
   186  
   187  	assert.Equal(t, expErr, pres.Connect(ctx))
   188  
   189  	_, _, err := pres.ReadBatch(ctx)
   190  	assert.Equal(t, expErr, err)
   191  
   192  	_, aFn, err := pres.ReadBatch(ctx)
   193  	require.NoError(t, err)
   194  
   195  	assert.Equal(t, expErr, aFn(ctx, nil))
   196  }
   197  
   198  func TestBatchAutoRetryErrorBackoff(t *testing.T) {
   199  	t.Parallel()
   200  
   201  	readerImpl := newMockBatchInput()
   202  	pres := AutoRetryNacksBatched(readerImpl)
   203  
   204  	go func() {
   205  		select {
   206  		case readerImpl.connChan <- nil:
   207  		case <-time.After(time.Second):
   208  			t.Error("Timed out")
   209  		}
   210  		select {
   211  		case readerImpl.readChan <- nil:
   212  		case <-time.After(time.Second):
   213  			t.Error("Timed out")
   214  		}
   215  		select {
   216  		case readerImpl.closeChan <- nil:
   217  		case <-time.After(time.Second):
   218  			t.Error("Timed out")
   219  		}
   220  	}()
   221  
   222  	ctx, cancel := context.WithTimeout(context.Background(), time.Millisecond*500)
   223  	defer cancel()
   224  
   225  	require.NoError(t, pres.Connect(ctx))
   226  
   227  	i := 0
   228  	for {
   229  		_, aFn, actErr := pres.ReadBatch(ctx)
   230  		if actErr != nil {
   231  			assert.EqualError(t, actErr, "context deadline exceeded")
   232  			break
   233  		}
   234  		require.NoError(t, aFn(ctx, errors.New("no thanks")))
   235  		i++
   236  		if i == 10 {
   237  			t.Error("Expected backoff to prevent this")
   238  			break
   239  		}
   240  	}
   241  
   242  	require.NoError(t, pres.Close(context.Background()))
   243  }
   244  
   245  func TestBatchAutoRetryBuffer(t *testing.T) {
   246  	t.Parallel()
   247  
   248  	ctx, cancel := context.WithTimeout(context.Background(), time.Second*2)
   249  	defer cancel()
   250  
   251  	readerImpl := newMockBatchInput()
   252  	pres := AutoRetryNacksBatched(readerImpl)
   253  
   254  	sendMsg := func(content string) {
   255  		readerImpl.msgsToSnd = []MessageBatch{
   256  			{NewMessage([]byte(content))},
   257  		}
   258  		select {
   259  		case readerImpl.readChan <- nil:
   260  		case <-time.After(time.Second):
   261  			t.Error("Timed out")
   262  		}
   263  	}
   264  	sendAck := func() {
   265  		select {
   266  		case readerImpl.ackChan <- nil:
   267  		case <-time.After(time.Second):
   268  			t.Error("Timed out")
   269  		}
   270  	}
   271  
   272  	// Send message normally.
   273  	exp := "msg 1"
   274  	exp2 := "msg 2"
   275  	exp3 := "msg 3"
   276  
   277  	go sendMsg(exp)
   278  	msg, aFn, err := pres.ReadBatch(ctx)
   279  	require.NoError(t, err)
   280  	require.Len(t, msg, 1)
   281  
   282  	b, err := msg[0].AsBytes()
   283  	require.NoError(t, err)
   284  	assert.Equal(t, exp, string(b))
   285  
   286  	// Prime second message.
   287  	go sendMsg(exp2)
   288  
   289  	// Fail previous message, expecting it to be resent.
   290  	_ = aFn(ctx, errors.New("failed"))
   291  	msg, aFn, err = pres.ReadBatch(ctx)
   292  	require.NoError(t, err)
   293  	require.Len(t, msg, 1)
   294  
   295  	b, err = msg[0].AsBytes()
   296  	require.NoError(t, err)
   297  	assert.Equal(t, exp, string(b))
   298  
   299  	// Read the primed message.
   300  	var aFn2 AckFunc
   301  	msg, aFn2, err = pres.ReadBatch(ctx)
   302  	require.NoError(t, err)
   303  	require.Len(t, msg, 1)
   304  
   305  	b, err = msg[0].AsBytes()
   306  	require.NoError(t, err)
   307  	assert.Equal(t, exp2, string(b))
   308  
   309  	// Fail both messages, expecting them to be resent.
   310  	_ = aFn(ctx, errors.New("failed again"))
   311  	_ = aFn2(ctx, errors.New("failed again"))
   312  
   313  	// Read both messages.
   314  	msg, aFn, err = pres.ReadBatch(ctx)
   315  	require.NoError(t, err)
   316  	require.Len(t, msg, 1)
   317  
   318  	b, err = msg[0].AsBytes()
   319  	require.NoError(t, err)
   320  	assert.Equal(t, exp, string(b))
   321  
   322  	msg, aFn2, err = pres.ReadBatch(ctx)
   323  	require.NoError(t, err)
   324  	require.Len(t, msg, 1)
   325  
   326  	b, err = msg[0].AsBytes()
   327  	require.NoError(t, err)
   328  	assert.Equal(t, exp2, string(b))
   329  
   330  	// Prime a new message and also an acknowledgement.
   331  	go sendMsg(exp3)
   332  	go sendAck()
   333  	go sendAck()
   334  
   335  	// Ack all messages.
   336  	_ = aFn(ctx, nil)
   337  	_ = aFn2(ctx, nil)
   338  
   339  	msg, _, err = pres.ReadBatch(ctx)
   340  	require.NoError(t, err)
   341  	require.Len(t, msg, 1)
   342  
   343  	b, err = msg[0].AsBytes()
   344  	require.NoError(t, err)
   345  	assert.Equal(t, exp3, string(b))
   346  }