github.com/Jeffail/benthos/v3@v3.65.0/lib/input/async_reader_test.go (about)

     1  package input
     2  
     3  import (
     4  	"context"
     5  	"errors"
     6  	"fmt"
     7  	"os"
     8  	"reflect"
     9  	"runtime/pprof"
    10  	"sync"
    11  	"testing"
    12  	"time"
    13  
    14  	"github.com/Jeffail/benthos/v3/lib/input/reader"
    15  	"github.com/Jeffail/benthos/v3/lib/log"
    16  	"github.com/Jeffail/benthos/v3/lib/message"
    17  	"github.com/Jeffail/benthos/v3/lib/metrics"
    18  	"github.com/Jeffail/benthos/v3/lib/response"
    19  	"github.com/Jeffail/benthos/v3/lib/types"
    20  	"github.com/stretchr/testify/assert"
    21  	"github.com/stretchr/testify/require"
    22  )
    23  
    24  //------------------------------------------------------------------------------
    25  
    26  type mockAsyncReader struct {
    27  	msgsToSnd []types.Message
    28  	ackRcvd   []error
    29  	ackMut    sync.Mutex
    30  
    31  	connChan       chan error
    32  	readChan       chan error
    33  	ackChan        chan error
    34  	closeAsyncChan chan struct{}
    35  	closeAsyncOnce sync.Once
    36  }
    37  
    38  func newMockAsyncReader() *mockAsyncReader {
    39  	return &mockAsyncReader{
    40  		connChan:       make(chan error),
    41  		readChan:       make(chan error),
    42  		ackChan:        make(chan error),
    43  		closeAsyncChan: make(chan struct{}),
    44  	}
    45  }
    46  
    47  func (r *mockAsyncReader) ConnectWithContext(ctx context.Context) error {
    48  	cerr, open := <-r.connChan
    49  	if !open {
    50  		return types.ErrNotConnected
    51  	}
    52  	return cerr
    53  }
    54  
    55  func (r *mockAsyncReader) ReadWithContext(ctx context.Context) (types.Message, reader.AsyncAckFn, error) {
    56  	select {
    57  	case <-ctx.Done():
    58  		return nil, nil, types.ErrTimeout
    59  	case err, open := <-r.readChan:
    60  		if !open {
    61  			return nil, nil, types.ErrNotConnected
    62  		}
    63  		if err != nil {
    64  			return nil, nil, err
    65  		}
    66  	}
    67  	r.ackMut.Lock()
    68  	r.ackRcvd = append(r.ackRcvd, errors.New("ack not received"))
    69  	i := len(r.ackRcvd) - 1
    70  	r.ackMut.Unlock()
    71  
    72  	var nextMsg types.Message = message.New(nil)
    73  	if len(r.msgsToSnd) > 0 {
    74  		nextMsg = r.msgsToSnd[0]
    75  		r.msgsToSnd = r.msgsToSnd[1:]
    76  	}
    77  
    78  	return nextMsg.DeepCopy(), func(ctx context.Context, res types.Response) error {
    79  		if res.SkipAck() {
    80  			return nil
    81  		}
    82  		r.ackMut.Lock()
    83  		r.ackRcvd[i] = res.Error()
    84  		r.ackMut.Unlock()
    85  		select {
    86  		case err := <-r.ackChan:
    87  			return err
    88  		case <-ctx.Done():
    89  		}
    90  		return nil
    91  	}, nil
    92  }
    93  
    94  func (r *mockAsyncReader) CloseAsync() {
    95  	r.closeAsyncOnce.Do(func() {
    96  		close(r.closeAsyncChan)
    97  	})
    98  }
    99  
   100  func (r *mockAsyncReader) WaitForClose(time.Duration) error {
   101  	return nil
   102  }
   103  
   104  //------------------------------------------------------------------------------
   105  
   106  type asyncReaderCantConnect struct{}
   107  
   108  func (r asyncReaderCantConnect) ConnectWithContext(ctx context.Context) error {
   109  	return types.ErrNotConnected
   110  }
   111  func (r asyncReaderCantConnect) ReadWithContext(ctx context.Context) (types.Message, reader.AsyncAckFn, error) {
   112  	return nil, nil, types.ErrNotConnected
   113  }
   114  func (r asyncReaderCantConnect) CloseAsync() {}
   115  func (r asyncReaderCantConnect) WaitForClose(time.Duration) error {
   116  	return nil
   117  }
   118  
   119  func TestAsyncReaderCantConnect(t *testing.T) {
   120  	r, err := NewAsyncReader(
   121  		"foo", true, asyncReaderCantConnect{},
   122  		log.Noop(), metrics.Noop(),
   123  	)
   124  	if err != nil {
   125  		t.Error(err)
   126  		return
   127  	}
   128  
   129  	// We will fail to connect but should still exit immediately.
   130  	r.CloseAsync()
   131  	if err = r.WaitForClose(time.Second); err != nil {
   132  		t.Error(err)
   133  	}
   134  }
   135  
   136  //------------------------------------------------------------------------------
   137  
   138  type asyncReaderCantRead struct {
   139  	connected int
   140  }
   141  
   142  func (r *asyncReaderCantRead) ConnectWithContext(ctx context.Context) error {
   143  	r.connected++
   144  	return nil
   145  }
   146  func (r *asyncReaderCantRead) ReadWithContext(ctx context.Context) (types.Message, reader.AsyncAckFn, error) {
   147  	return nil, nil, types.ErrNotConnected
   148  }
   149  func (r *asyncReaderCantRead) CloseAsync() {}
   150  func (r *asyncReaderCantRead) WaitForClose(time.Duration) error {
   151  	return nil
   152  }
   153  
   154  func TestAsyncReaderCantRead(t *testing.T) {
   155  	readerImpl := &asyncReaderCantRead{}
   156  
   157  	r, err := NewAsyncReader(
   158  		"foo", true, readerImpl,
   159  		log.Noop(), metrics.Noop(),
   160  	)
   161  	if err != nil {
   162  		t.Error(err)
   163  		return
   164  	}
   165  
   166  	// We will be failing to send but should still exit immediately.
   167  	r.CloseAsync()
   168  	if err = r.WaitForClose(time.Second); err != nil {
   169  		t.Error(err)
   170  	}
   171  
   172  	if readerImpl.connected < 1 {
   173  		t.Errorf("Connected wasn't called enough times: %v", readerImpl.connected)
   174  	}
   175  }
   176  
   177  //------------------------------------------------------------------------------
   178  
   179  func TestAsyncReaderTypeClosedOnConn(t *testing.T) {
   180  	readerImpl := newMockAsyncReader()
   181  
   182  	r, err := NewAsyncReader(
   183  		"foo", true, readerImpl,
   184  		log.Noop(), metrics.Noop(),
   185  	)
   186  	if err != nil {
   187  		t.Error(err)
   188  		return
   189  	}
   190  
   191  	go func() {
   192  		select {
   193  		case readerImpl.connChan <- types.ErrTypeClosed:
   194  		case <-time.After(time.Second):
   195  		}
   196  	}()
   197  
   198  	if err = r.WaitForClose(time.Second); err != nil {
   199  		t.Error(err)
   200  	}
   201  }
   202  
   203  func TestAsyncReaderTypeClosedOnReconn(t *testing.T) {
   204  	readerImpl := newMockAsyncReader()
   205  
   206  	r, err := NewAsyncReader(
   207  		"foo", true, readerImpl,
   208  		log.Noop(), metrics.Noop(),
   209  	)
   210  	if err != nil {
   211  		t.Error(err)
   212  		return
   213  	}
   214  
   215  	go func() {
   216  		select {
   217  		case readerImpl.connChan <- nil:
   218  		case <-time.After(time.Second):
   219  		}
   220  		select {
   221  		case readerImpl.readChan <- types.ErrNotConnected:
   222  		case <-time.After(time.Second):
   223  		}
   224  		select {
   225  		case readerImpl.connChan <- types.ErrTypeClosed:
   226  		case <-time.After(time.Second):
   227  		}
   228  	}()
   229  
   230  	if err = r.WaitForClose(time.Second); err != nil {
   231  		t.Error(err)
   232  	}
   233  }
   234  
   235  func TestAsyncReaderTypeClosedOnReread(t *testing.T) {
   236  	readerImpl := newMockAsyncReader()
   237  
   238  	r, err := NewAsyncReader(
   239  		"foo", true, readerImpl,
   240  		log.Noop(), metrics.Noop(),
   241  	)
   242  	if err != nil {
   243  		t.Fatal(err)
   244  	}
   245  
   246  	go func() {
   247  		select {
   248  		case readerImpl.connChan <- nil:
   249  		case <-time.After(time.Second):
   250  		}
   251  		select {
   252  		case readerImpl.readChan <- types.ErrNotConnected:
   253  		case <-time.After(time.Second):
   254  		}
   255  		select {
   256  		case readerImpl.connChan <- nil:
   257  		case <-time.After(time.Second):
   258  		}
   259  		select {
   260  		case readerImpl.readChan <- types.ErrTypeClosed:
   261  		case <-time.After(time.Second):
   262  		}
   263  	}()
   264  
   265  	if err = r.WaitForClose(time.Second); err != nil {
   266  		t.Error(err)
   267  	}
   268  }
   269  
   270  //------------------------------------------------------------------------------
   271  
   272  func TestAsyncReaderCanReconnect(t *testing.T) {
   273  	readerImpl := newMockAsyncReader()
   274  
   275  	r, err := NewAsyncReader(
   276  		"foo", true, readerImpl,
   277  		log.Noop(), metrics.Noop(),
   278  	)
   279  	if err != nil {
   280  		t.Error(err)
   281  		return
   282  	}
   283  
   284  	go func() {
   285  		select {
   286  		case readerImpl.connChan <- nil:
   287  		case <-time.After(time.Second):
   288  		}
   289  		select {
   290  		case readerImpl.readChan <- types.ErrNotConnected:
   291  		case <-time.After(time.Second):
   292  		}
   293  		select {
   294  		case readerImpl.connChan <- nil:
   295  		case <-time.After(time.Second):
   296  		}
   297  		select {
   298  		case readerImpl.readChan <- nil:
   299  		case <-time.After(time.Second):
   300  		}
   301  		select {
   302  		case readerImpl.ackChan <- nil:
   303  		case <-time.After(time.Second):
   304  		}
   305  	}()
   306  
   307  	var ts types.Transaction
   308  	var open bool
   309  	select {
   310  	case ts, open = <-r.TransactionChan():
   311  		if !open {
   312  			t.Fatal("Closed early")
   313  		}
   314  	case <-time.After(time.Second):
   315  		t.Error("Timed out")
   316  	}
   317  
   318  	select {
   319  	case ts.ResponseChan <- response.NewAck():
   320  	case <-time.After(time.Second):
   321  		t.Error("Timed out")
   322  	}
   323  
   324  	// We will be failing to send but should still exit immediately.
   325  	r.CloseAsync()
   326  
   327  	go func() {
   328  		select {
   329  		case readerImpl.readChan <- nil:
   330  		case readerImpl.connChan <- types.ErrNotConnected:
   331  		case <-time.After(time.Second):
   332  		}
   333  	}()
   334  
   335  	if err = r.WaitForClose(time.Second); err != nil {
   336  		t.Error(err)
   337  	}
   338  }
   339  
   340  func TestAsyncReaderFailsReconnect(t *testing.T) {
   341  	readerImpl := newMockAsyncReader()
   342  
   343  	r, err := NewAsyncReader(
   344  		"foo", true, readerImpl,
   345  		log.Noop(), metrics.Noop(),
   346  	)
   347  	if err != nil {
   348  		t.Error(err)
   349  		return
   350  	}
   351  
   352  	go func() {
   353  		select {
   354  		case readerImpl.connChan <- nil:
   355  		case <-time.After(time.Second):
   356  		}
   357  		select {
   358  		case readerImpl.readChan <- types.ErrNotConnected:
   359  		case <-time.After(time.Second):
   360  		}
   361  		select {
   362  		case readerImpl.connChan <- types.ErrNotConnected:
   363  		case <-time.After(time.Second):
   364  		}
   365  		select {
   366  		case readerImpl.connChan <- nil:
   367  		case <-time.After(time.Second * 2):
   368  		}
   369  		select {
   370  		case readerImpl.readChan <- nil:
   371  		case <-time.After(time.Second):
   372  		}
   373  		select {
   374  		case readerImpl.ackChan <- nil:
   375  		case <-time.After(time.Second):
   376  		}
   377  	}()
   378  
   379  	var ts types.Transaction
   380  	var open bool
   381  	select {
   382  	case ts, open = <-r.TransactionChan():
   383  		if !open {
   384  			t.Fatal("Closed early")
   385  		}
   386  	case <-time.After(time.Second * 2):
   387  		t.Error("Timed out")
   388  	}
   389  
   390  	select {
   391  	case ts.ResponseChan <- response.NewAck():
   392  	case <-time.After(time.Second):
   393  		t.Error("Timed out")
   394  	}
   395  
   396  	// We will be failing to send but should still exit immediately.
   397  	r.CloseAsync()
   398  
   399  	go func() {
   400  		select {
   401  		case readerImpl.readChan <- nil:
   402  		case <-time.After(time.Second):
   403  		}
   404  	}()
   405  
   406  	if err = r.WaitForClose(time.Second); err != nil {
   407  		t.Error(err)
   408  	}
   409  }
   410  
   411  func TestAsyncReaderCloseDuringReconnect(t *testing.T) {
   412  	readerImpl := newMockAsyncReader()
   413  
   414  	r, err := NewAsyncReader(
   415  		"foo", true, readerImpl,
   416  		log.Noop(), metrics.Noop(),
   417  	)
   418  	if err != nil {
   419  		t.Fatal(err)
   420  	}
   421  
   422  	select {
   423  	case readerImpl.connChan <- nil:
   424  	case <-time.After(time.Second):
   425  		t.Fatal("Timed out")
   426  	}
   427  	select {
   428  	case readerImpl.readChan <- types.ErrNotConnected:
   429  	case <-time.After(time.Second):
   430  		t.Fatal("Timed out")
   431  	}
   432  
   433  	go func() {
   434  		select {
   435  		case readerImpl.connChan <- types.ErrNotConnected:
   436  		case <-time.After(time.Second):
   437  		}
   438  		close(readerImpl.connChan)
   439  	}()
   440  
   441  	// We will be failing to send but should still exit immediately.
   442  	r.CloseAsync()
   443  	close(readerImpl.readChan)
   444  
   445  	if err = r.WaitForClose(time.Second); err != nil {
   446  		pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
   447  		t.Error(err)
   448  	}
   449  }
   450  
   451  func TestAsyncReaderHappyPath(t *testing.T) {
   452  	exp := [][]byte{[]byte("foo"), []byte("bar")}
   453  
   454  	readerImpl := newMockAsyncReader()
   455  	readerImpl.msgsToSnd = []types.Message{message.New(exp)}
   456  
   457  	r, err := NewAsyncReader(
   458  		"foo", true, readerImpl,
   459  		log.Noop(), metrics.Noop(),
   460  	)
   461  	if err != nil {
   462  		t.Fatal(err)
   463  	}
   464  
   465  	select {
   466  	case readerImpl.connChan <- nil:
   467  	case <-time.After(time.Second):
   468  		t.Fatal("Timed out")
   469  	}
   470  
   471  	go func() {
   472  		select {
   473  		case readerImpl.readChan <- nil:
   474  		case <-time.After(time.Second):
   475  		}
   476  		select {
   477  		case readerImpl.ackChan <- nil:
   478  		case <-time.After(time.Second):
   479  		}
   480  	}()
   481  
   482  	var ts types.Transaction
   483  	var open bool
   484  
   485  	select {
   486  	case ts, open = <-r.TransactionChan():
   487  		if !open {
   488  			t.Fatal("Chan closed")
   489  		}
   490  		if act := message.GetAllBytes(ts.Payload); !reflect.DeepEqual(exp, act) {
   491  			t.Errorf("Wrong message returned: %v != %v", act, exp)
   492  		}
   493  	case <-time.After(time.Second):
   494  		t.Fatal("Timed out")
   495  	}
   496  
   497  	select {
   498  	case ts.ResponseChan <- response.NewAck():
   499  	case <-time.After(time.Second):
   500  		t.Fatal("Timed out")
   501  	}
   502  
   503  	// We will be failing to send but should still exit immediately.
   504  	r.CloseAsync()
   505  	close(readerImpl.readChan)
   506  	close(readerImpl.connChan)
   507  
   508  	if err = r.WaitForClose(time.Second); err != nil {
   509  		pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
   510  		t.Fatal(err)
   511  	}
   512  
   513  	if readerImpl.ackRcvd[0] != nil {
   514  		t.Error(readerImpl.ackRcvd[0])
   515  	}
   516  }
   517  
   518  func TestAsyncReaderCloseWithPendingAcks(t *testing.T) {
   519  	exp := [][]byte{[]byte("hello world")}
   520  
   521  	readerImpl := newMockAsyncReader()
   522  	readerImpl.msgsToSnd = []types.Message{message.New(exp)}
   523  
   524  	r, err := NewAsyncReader("foo", true, readerImpl, log.Noop(), metrics.Noop())
   525  	require.NoError(t, err)
   526  
   527  	select {
   528  	case readerImpl.connChan <- nil:
   529  	case <-time.After(time.Second):
   530  		t.Fatal("Timed out")
   531  	}
   532  
   533  	go func() {
   534  		select {
   535  		case readerImpl.readChan <- nil:
   536  		case <-time.After(time.Second):
   537  		}
   538  	}()
   539  
   540  	var ts types.Transaction
   541  	var open bool
   542  
   543  	select {
   544  	case ts, open = <-r.TransactionChan():
   545  		require.True(t, open)
   546  		assert.Equal(t, message.GetAllBytes(ts.Payload), exp)
   547  	case <-time.After(time.Second):
   548  		t.Fatal("Timed out")
   549  	}
   550  
   551  	select {
   552  	case ts.ResponseChan <- response.NewAck():
   553  	case <-time.After(time.Second):
   554  		t.Fatal("Timed out")
   555  	}
   556  
   557  	// Blocking the reader ack for now
   558  	r.CloseAsync()
   559  
   560  	select {
   561  	case <-readerImpl.closeAsyncChan:
   562  		t.Fatal("reader closed early")
   563  	// case <-time.After(time.Millisecond * 100):
   564  	case <-time.After(time.Second):
   565  	}
   566  
   567  	select {
   568  	case readerImpl.ackChan <- nil:
   569  	case <-time.After(time.Second):
   570  		t.Fatal("Timed out")
   571  	}
   572  
   573  	select {
   574  	case <-readerImpl.closeAsyncChan:
   575  	case <-time.After(time.Second):
   576  		t.Fatal("Timed out")
   577  	}
   578  
   579  	if readerImpl.ackRcvd[0] != nil {
   580  		t.Error(readerImpl.ackRcvd[0])
   581  	}
   582  }
   583  
   584  func TestAsyncReaderSadPath(t *testing.T) {
   585  	exp := [][]byte{[]byte("foo"), []byte("bar")}
   586  	expErr := errors.New("test error")
   587  
   588  	readerImpl := newMockAsyncReader()
   589  	readerImpl.msgsToSnd = []types.Message{message.New(exp)}
   590  
   591  	r, err := NewAsyncReader(
   592  		"foo", true, readerImpl,
   593  		log.Noop(), metrics.Noop(),
   594  	)
   595  	if err != nil {
   596  		t.Fatal(err)
   597  	}
   598  
   599  	select {
   600  	case readerImpl.connChan <- nil:
   601  	case <-time.After(time.Second):
   602  		t.Fatal("Timed out")
   603  	}
   604  
   605  	go func() {
   606  		for {
   607  			select {
   608  			case readerImpl.readChan <- nil:
   609  				select {
   610  				case readerImpl.ackChan <- nil:
   611  				case <-time.After(time.Second):
   612  				}
   613  				return
   614  			case readerImpl.connChan <- nil:
   615  			case <-time.After(time.Second):
   616  			}
   617  		}
   618  	}()
   619  
   620  	var ts types.Transaction
   621  	var open bool
   622  
   623  	select {
   624  	case ts, open = <-r.TransactionChan():
   625  		if !open {
   626  			t.Fatal("Chan closed")
   627  		}
   628  		if act := message.GetAllBytes(ts.Payload); !reflect.DeepEqual(exp, act) {
   629  			t.Errorf("Wrong message returned: %v != %v", act, exp)
   630  		}
   631  	case <-time.After(time.Second):
   632  		t.Fatal("Timed out")
   633  	}
   634  
   635  	select {
   636  	case ts.ResponseChan <- response.NewError(expErr):
   637  	case <-time.After(time.Second):
   638  		t.Fatal("Timed out")
   639  	}
   640  
   641  	// We will be failing to send but should still exit immediately.
   642  	r.CloseAsync()
   643  	close(readerImpl.readChan)
   644  	close(readerImpl.connChan)
   645  
   646  	if err = r.WaitForClose(time.Second); err != nil {
   647  		pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
   648  		t.Fatal(err)
   649  	}
   650  
   651  	if actErr := readerImpl.ackRcvd[0]; expErr != actErr {
   652  		t.Errorf("Wrong response received: %v != %v", actErr, expErr)
   653  	}
   654  }
   655  
   656  func TestAsyncReaderParallel(t *testing.T) {
   657  	expMsgs := []string{}
   658  	for i := 0; i < 10; i++ {
   659  		expMsgs = append(expMsgs, fmt.Sprintf("message: %v", i))
   660  	}
   661  	readerImpl := newMockAsyncReader()
   662  	for _, str := range expMsgs {
   663  		readerImpl.msgsToSnd = append(readerImpl.msgsToSnd, message.New([][]byte{[]byte(str)}))
   664  	}
   665  
   666  	r, err := NewAsyncReader(
   667  		"foo", true, readerImpl,
   668  		log.Noop(), metrics.Noop(),
   669  	)
   670  	if err != nil {
   671  		t.Fatal(err)
   672  	}
   673  
   674  	select {
   675  	case readerImpl.connChan <- nil:
   676  	case <-time.After(time.Second):
   677  		t.Fatal("Timed out")
   678  	}
   679  
   680  	go func() {
   681  		for range expMsgs {
   682  			select {
   683  			case readerImpl.readChan <- nil:
   684  			case <-time.After(time.Second):
   685  			}
   686  		}
   687  	}()
   688  
   689  	expErrs := []error{}
   690  	for i := range expMsgs {
   691  		expErrs = append(expErrs, fmt.Errorf("err %v", i))
   692  	}
   693  
   694  	resChans := make([]chan<- types.Response, len(expMsgs))
   695  	for i, mStr := range expMsgs {
   696  		var ts types.Transaction
   697  		var open bool
   698  		select {
   699  		case ts, open = <-r.TransactionChan():
   700  			if !open {
   701  				t.Fatal("Chan closed")
   702  			}
   703  			if act, exp := string(ts.Payload.Get(0).Get()), mStr; exp != act {
   704  				t.Errorf("Wrong message returned: %v != %v", act, exp)
   705  			}
   706  			resChans[i] = ts.ResponseChan
   707  		case <-time.After(time.Second):
   708  			t.Fatal("Timed out")
   709  		}
   710  	}
   711  
   712  	go func() {
   713  		for range expErrs {
   714  			select {
   715  			case readerImpl.ackChan <- nil:
   716  			case <-time.After(time.Second):
   717  			}
   718  		}
   719  	}()
   720  
   721  	for i, e := range expErrs {
   722  		select {
   723  		case resChans[i] <- response.NewError(e):
   724  		case <-time.After(time.Second):
   725  			t.Fatal("Timed out")
   726  		}
   727  	}
   728  
   729  	// We will be failing to send but should still exit immediately.
   730  	r.CloseAsync()
   731  	close(readerImpl.readChan)
   732  	close(readerImpl.connChan)
   733  
   734  	if err = r.WaitForClose(time.Second); err != nil {
   735  		pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
   736  		t.Fatal(err)
   737  	}
   738  
   739  	if exp, act := expErrs, readerImpl.ackRcvd; !reflect.DeepEqual(exp, act) {
   740  		t.Errorf("Unexpected errors returned: %v != %v", act, exp)
   741  	}
   742  }
   743  
   744  func TestAsyncReaderSkipAcksAMO(t *testing.T) {
   745  	exp := [][]byte{[]byte("foo"), []byte("bar")}
   746  
   747  	readerImpl := newMockAsyncReader()
   748  	readerImpl.msgsToSnd = []types.Message{
   749  		message.New(exp),
   750  		message.New(exp),
   751  		message.New(exp),
   752  	}
   753  
   754  	r, err := NewAsyncReader(
   755  		"foo", true, readerImpl,
   756  		log.Noop(), metrics.Noop(),
   757  	)
   758  	if err != nil {
   759  		t.Error(err)
   760  		return
   761  	}
   762  
   763  	select {
   764  	case readerImpl.connChan <- nil:
   765  	case <-time.After(time.Second):
   766  		t.Fatal("Timed out")
   767  	}
   768  
   769  	for i := 0; i < 3; i++ {
   770  		go func() {
   771  			select {
   772  			case readerImpl.readChan <- nil:
   773  			case <-time.After(time.Second):
   774  			}
   775  		}()
   776  
   777  		var ts types.Transaction
   778  		var open bool
   779  		select {
   780  		case ts, open = <-r.TransactionChan():
   781  			if !open {
   782  				t.Fatal("Chan closed")
   783  			}
   784  			if act := message.GetAllBytes(ts.Payload); !reflect.DeepEqual(exp, act) {
   785  				t.Errorf("Wrong message returned: %s != %s", act, exp)
   786  			}
   787  		case <-time.After(time.Second):
   788  			t.Fatalf("Timed out at attempt: %v", i)
   789  		}
   790  
   791  		select {
   792  		case ts.ResponseChan <- response.NewUnack():
   793  		case <-time.After(time.Second):
   794  			t.Fatal("Timed out")
   795  		}
   796  	}
   797  
   798  	// We will be failing to send but should still exit immediately.
   799  	r.CloseAsync()
   800  	close(readerImpl.readChan)
   801  	close(readerImpl.connChan)
   802  
   803  	if err = r.WaitForClose(time.Second); err != nil {
   804  		pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
   805  		t.Error(err)
   806  	}
   807  
   808  	expErr := "ack not received"
   809  	if actErr := readerImpl.ackRcvd[0].Error(); expErr != actErr {
   810  		t.Errorf("Wrong response received: %v != %v", actErr, expErr)
   811  	}
   812  }
   813  
   814  func TestAsyncReaderSkipAcksALO(t *testing.T) {
   815  	exp := [][]byte{[]byte("foo"), []byte("bar")}
   816  
   817  	readerImpl := newMockAsyncReader()
   818  	readerImpl.msgsToSnd = []types.Message{
   819  		message.New(exp),
   820  		message.New(exp),
   821  		message.New(exp),
   822  	}
   823  
   824  	r, err := NewAsyncReader(
   825  		"foo", false, readerImpl,
   826  		log.Noop(), metrics.Noop(),
   827  	)
   828  	if err != nil {
   829  		t.Error(err)
   830  		return
   831  	}
   832  
   833  	select {
   834  	case readerImpl.connChan <- nil:
   835  	case <-time.After(time.Second):
   836  		t.Fatal("Timed out")
   837  	}
   838  
   839  	go func() {
   840  		select {
   841  		case readerImpl.readChan <- nil:
   842  		case <-time.After(time.Second):
   843  		}
   844  	}()
   845  
   846  	var ts types.Transaction
   847  	var open bool
   848  	select {
   849  	case ts, open = <-r.TransactionChan():
   850  		if !open {
   851  			t.Fatal("Chan closed")
   852  		}
   853  		if act := message.GetAllBytes(ts.Payload); !reflect.DeepEqual(exp, act) {
   854  			t.Errorf("Wrong message returned: %s != %s", act, exp)
   855  		}
   856  	case <-time.After(time.Second):
   857  		t.Fatal("Timed out")
   858  	}
   859  
   860  	select {
   861  	case ts.ResponseChan <- response.NewUnack():
   862  	case <-time.After(time.Second):
   863  		t.Fatal("Timed out")
   864  	}
   865  
   866  	select {
   867  	case readerImpl.ackChan <- nil:
   868  	case <-time.After(time.Second):
   869  	}
   870  
   871  	// Show be closing down.
   872  	if err = r.WaitForClose(time.Second); err != nil {
   873  		pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
   874  		t.Error(err)
   875  	}
   876  
   877  	expErr := "message failed to reach a target destination"
   878  	if actErr := readerImpl.ackRcvd[0].Error(); expErr != actErr {
   879  		t.Errorf("Wrong response received: %v != %v", actErr, expErr)
   880  	}
   881  }
   882  
   883  //------------------------------------------------------------------------------
   884  
   885  func BenchmarkAsyncReaderGenerateN1(b *testing.B) {
   886  	benchmarkAsyncReaderGenerateN(b, 1)
   887  }
   888  
   889  func BenchmarkAsyncReaderGenerateN10(b *testing.B) {
   890  	benchmarkAsyncReaderGenerateN(b, 10)
   891  }
   892  
   893  func BenchmarkAsyncReaderGenerateN100(b *testing.B) {
   894  	benchmarkAsyncReaderGenerateN(b, 100)
   895  }
   896  
   897  func BenchmarkAsyncReaderGenerateN1000(b *testing.B) {
   898  	benchmarkAsyncReaderGenerateN(b, 1000)
   899  }
   900  
   901  func benchmarkAsyncReaderGenerateN(b *testing.B, capacity int) {
   902  	bloblConf := NewBloblangConfig()
   903  	bloblConf.Count = 0
   904  	bloblConf.Interval = ""
   905  	bloblConf.Mapping = `root = "hello world"`
   906  
   907  	readerImpl, err := newBloblang(types.NoopMgr(), bloblConf)
   908  	require.NoError(b, err)
   909  
   910  	r, err := NewAsyncReader("foo", true, readerImpl, log.Noop(), metrics.Noop())
   911  	require.NoError(b, err)
   912  
   913  	b.Cleanup(func() {
   914  		r.CloseAsync()
   915  		if err = r.WaitForClose(time.Second); err != nil {
   916  			pprof.Lookup("goroutine").WriteTo(os.Stdout, 1)
   917  			b.Fatal(err)
   918  		}
   919  	})
   920  
   921  	resChans := make([]chan<- types.Response, capacity)
   922  
   923  	b.ReportAllocs()
   924  	b.ResetTimer()
   925  
   926  	for i := 0; i < b.N/capacity; i++ {
   927  		for j := 0; j < capacity; j++ {
   928  			select {
   929  			case ts, open := <-r.TransactionChan():
   930  				require.True(b, open)
   931  				resChans[j] = ts.ResponseChan
   932  			case <-time.After(time.Second):
   933  				b.Fatal("Timed out")
   934  			}
   935  		}
   936  
   937  		for j := 0; j < capacity; j++ {
   938  			select {
   939  			case resChans[j] <- response.NewAck():
   940  			case <-time.After(time.Second):
   941  				b.Fatal("Timed out")
   942  			}
   943  		}
   944  	}
   945  }