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 }