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

     1  package service
     2  
     3  import (
     4  	"context"
     5  	"sync"
     6  	"testing"
     7  	"time"
     8  
     9  	"github.com/Jeffail/benthos/v3/internal/component/buffer"
    10  	"github.com/Jeffail/benthos/v3/lib/log"
    11  	"github.com/Jeffail/benthos/v3/lib/message"
    12  	"github.com/Jeffail/benthos/v3/lib/metrics"
    13  	"github.com/Jeffail/benthos/v3/lib/response"
    14  	"github.com/Jeffail/benthos/v3/lib/types"
    15  	"github.com/stretchr/testify/assert"
    16  	"github.com/stretchr/testify/require"
    17  )
    18  
    19  type memoryBuffer struct {
    20  	messages       chan MessageBatch
    21  	endOfInputChan chan struct{}
    22  	closeOnce      sync.Once
    23  }
    24  
    25  func newMemoryBuffer(n int) *memoryBuffer {
    26  	return &memoryBuffer{
    27  		messages:       make(chan MessageBatch, n),
    28  		endOfInputChan: make(chan struct{}),
    29  	}
    30  }
    31  
    32  func (m *memoryBuffer) WriteBatch(ctx context.Context, batch MessageBatch, aFn AckFunc) error {
    33  	select {
    34  	case m.messages <- batch:
    35  	case <-ctx.Done():
    36  		return ctx.Err()
    37  	}
    38  	return aFn(context.Background(), nil)
    39  }
    40  
    41  func yoloIgnoreNacks(context.Context, error) error {
    42  	// YOLO: Drop messages that are nacked
    43  	return nil
    44  }
    45  
    46  func (m *memoryBuffer) ReadBatch(ctx context.Context) (MessageBatch, AckFunc, error) {
    47  	select {
    48  	case msg := <-m.messages:
    49  		return msg, yoloIgnoreNacks, nil
    50  	case <-ctx.Done():
    51  		return nil, nil, ctx.Err()
    52  	case <-m.endOfInputChan:
    53  		// Input has ended, so return ErrEndOfBuffer if our buffer is empty.
    54  		select {
    55  		case msg := <-m.messages:
    56  			return msg, yoloIgnoreNacks, nil
    57  		default:
    58  			return nil, nil, ErrEndOfBuffer
    59  		}
    60  	}
    61  }
    62  
    63  func (m *memoryBuffer) EndOfInput() {
    64  	m.closeOnce.Do(func() {
    65  		close(m.endOfInputChan)
    66  	})
    67  }
    68  
    69  func (m *memoryBuffer) Close(ctx context.Context) error {
    70  	// Nothing to clean up
    71  	return nil
    72  }
    73  
    74  func TestStreamMemoryBuffer(t *testing.T) {
    75  	var incr, total uint8 = 100, 50
    76  
    77  	tChan := make(chan types.Transaction)
    78  	resChan := make(chan types.Response)
    79  
    80  	b := buffer.NewStream("meow", newAirGapBatchBuffer(newMemoryBuffer(int(total))), log.Noop(), metrics.Noop())
    81  	require.NoError(t, b.Consume(tChan))
    82  
    83  	var i uint8
    84  
    85  	// Check correct flow no blocking
    86  	for ; i < total; i++ {
    87  		msgBytes := make([][]byte, 1)
    88  		msgBytes[0] = make([]byte, int(incr))
    89  		msgBytes[0][0] = i
    90  
    91  		select {
    92  		// Send to buffer
    93  		case tChan <- types.NewTransaction(message.New(msgBytes), resChan):
    94  		case <-time.After(time.Second):
    95  			t.Fatalf("Timed out waiting for unbuffered message %v send", i)
    96  		}
    97  
    98  		// Instant response from buffer
    99  		select {
   100  		case res := <-resChan:
   101  			require.NoError(t, res.Error())
   102  		case <-time.After(time.Second):
   103  			t.Fatalf("Timed out waiting for unbuffered message %v response", i)
   104  		}
   105  
   106  		// Receive on output
   107  		var outTr types.Transaction
   108  		select {
   109  		case outTr = <-b.TransactionChan():
   110  			assert.Equal(t, i, outTr.Payload.Get(0).Get()[0])
   111  		case <-time.After(time.Second):
   112  			t.Fatalf("Timed out waiting for unbuffered message %v read", i)
   113  		}
   114  
   115  		// Response from output
   116  		select {
   117  		case outTr.ResponseChan <- response.NewAck():
   118  		case <-time.After(time.Second):
   119  			t.Fatalf("Timed out waiting for unbuffered response send back %v", i)
   120  		}
   121  	}
   122  
   123  	for i = 0; i <= total; i++ {
   124  		msgBytes := make([][]byte, 1)
   125  		msgBytes[0] = make([]byte, int(incr))
   126  		msgBytes[0][0] = i
   127  
   128  		select {
   129  		case tChan <- types.NewTransaction(message.New(msgBytes), resChan):
   130  		case <-time.After(time.Second):
   131  			t.Fatalf("Timed out waiting for buffered message %v send", i)
   132  		}
   133  		select {
   134  		case res := <-resChan:
   135  			assert.NoError(t, res.Error())
   136  		case <-time.After(time.Second):
   137  			t.Fatalf("Timed out waiting for buffered message %v response", i)
   138  		}
   139  	}
   140  
   141  	// Should have reached limit here
   142  	msgBytes := make([][]byte, 1)
   143  	msgBytes[0] = make([]byte, int(incr)+1)
   144  
   145  	select {
   146  	case tChan <- types.NewTransaction(message.New(msgBytes), resChan):
   147  	case <-time.After(time.Second):
   148  		t.Fatalf("Timed out waiting for final buffered message send")
   149  	}
   150  
   151  	// Response should block until buffer is relieved
   152  	select {
   153  	case res := <-resChan:
   154  		if res.Error() != nil {
   155  			t.Fatal(res.Error())
   156  		} else {
   157  			t.Fatalf("Overflowed response returned before timeout")
   158  		}
   159  	case <-time.After(100 * time.Millisecond):
   160  	}
   161  
   162  	var outTr types.Transaction
   163  
   164  	// Extract last message
   165  	select {
   166  	case outTr = <-b.TransactionChan():
   167  		assert.Equal(t, byte(0), outTr.Payload.Get(0).Get()[0])
   168  		outTr.ResponseChan <- response.NewAck()
   169  	case <-time.After(time.Second):
   170  		t.Fatalf("Timed out waiting for final buffered message read")
   171  	}
   172  
   173  	// Response from the last attempt should no longer be blocking
   174  	select {
   175  	case res := <-resChan:
   176  		assert.NoError(t, res.Error())
   177  	case <-time.After(100 * time.Millisecond):
   178  		t.Errorf("Final buffered response blocked")
   179  	}
   180  
   181  	// Extract all other messages
   182  	for i = 1; i <= total; i++ {
   183  		select {
   184  		case outTr = <-b.TransactionChan():
   185  			assert.Equal(t, i, outTr.Payload.Get(0).Get()[0])
   186  		case <-time.After(time.Second):
   187  			t.Fatalf("Timed out waiting for buffered message %v read", i)
   188  		}
   189  
   190  		select {
   191  		case outTr.ResponseChan <- response.NewAck():
   192  		case <-time.After(time.Second):
   193  			t.Fatalf("Timed out waiting for buffered response send back %v", i)
   194  		}
   195  	}
   196  
   197  	// Get final message
   198  	select {
   199  	case outTr = <-b.TransactionChan():
   200  	case <-time.After(time.Second):
   201  		t.Fatalf("Timed out waiting for buffered message %v read", i)
   202  	}
   203  
   204  	select {
   205  	case outTr.ResponseChan <- response.NewAck():
   206  	case <-time.After(time.Second):
   207  		t.Fatalf("Timed out waiting for buffered response send back %v", i)
   208  	}
   209  
   210  	b.CloseAsync()
   211  	require.NoError(t, b.WaitForClose(time.Second))
   212  
   213  	close(resChan)
   214  	close(tChan)
   215  }
   216  
   217  func TestStreamBufferClosing(t *testing.T) {
   218  	var incr, total uint8 = 100, 5
   219  
   220  	tChan := make(chan types.Transaction)
   221  	resChan := make(chan types.Response)
   222  
   223  	b := buffer.NewStream("meow", newAirGapBatchBuffer(newMemoryBuffer(int(total))), log.Noop(), metrics.Noop())
   224  	require.NoError(t, b.Consume(tChan))
   225  
   226  	var i uint8
   227  
   228  	// Populate buffer with some messages
   229  	for i = 0; i < total; i++ {
   230  		msgBytes := make([][]byte, 1)
   231  		msgBytes[0] = make([]byte, int(incr))
   232  		msgBytes[0][0] = i
   233  
   234  		select {
   235  		case tChan <- types.NewTransaction(message.New(msgBytes), resChan):
   236  		case <-time.After(time.Second):
   237  			t.Fatalf("Timed out waiting for buffered message %v send", i)
   238  		}
   239  		select {
   240  		case res := <-resChan:
   241  			assert.NoError(t, res.Error())
   242  		case <-time.After(time.Second):
   243  			t.Fatalf("Timed out waiting for buffered message %v response", i)
   244  		}
   245  	}
   246  
   247  	// Close input, this should prompt the stack buffer to Flush().
   248  	close(tChan)
   249  
   250  	// Receive all of those messages from the buffer
   251  	for i = 0; i < total; i++ {
   252  		select {
   253  		case val := <-b.TransactionChan():
   254  			assert.Equal(t, i, val.Payload.Get(0).Get()[0])
   255  			val.ResponseChan <- response.NewAck()
   256  		case <-time.After(time.Second):
   257  			t.Fatalf("Timed out waiting for final buffered message read")
   258  		}
   259  	}
   260  
   261  	// The buffer should now be closed, therefore so should our read channel.
   262  	select {
   263  	case _, open := <-b.TransactionChan():
   264  		assert.False(t, open)
   265  	case <-time.After(time.Second):
   266  		t.Fatalf("Timed out waiting for final buffered message read")
   267  	}
   268  
   269  	// Should already be shut down.
   270  	assert.NoError(t, b.WaitForClose(time.Second))
   271  }