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 }