github.com/iotexproject/iotex-core@v1.14.1-rc1/pkg/messagebatcher/batchwriter_test.go (about)

     1  package batch
     2  
     3  import (
     4  	"math/big"
     5  	"runtime"
     6  	"sync/atomic"
     7  	"testing"
     8  	"time"
     9  
    10  	"github.com/iotexproject/iotex-proto/golang/iotextypes"
    11  	"github.com/libp2p/go-libp2p-core/peer"
    12  	"github.com/multiformats/go-multiaddr"
    13  	"github.com/stretchr/testify/require"
    14  	"google.golang.org/protobuf/proto"
    15  
    16  	"github.com/iotexproject/iotex-core/action"
    17  	"github.com/iotexproject/iotex-core/test/identityset"
    18  	"github.com/iotexproject/iotex-core/testutil"
    19  )
    20  
    21  var (
    22  	peerAddr1, _ = peer.AddrInfoFromP2pAddr(multiaddr.StringCast(`/ip4/127.0.0.1/tcp/31954/p2p/12D3KooWJwW7pUpTkxPTMv84RPLPMQVEAjZ6fvJuX4oZrvW5DAGQ`))
    23  	peerAddr2, _ = peer.AddrInfoFromP2pAddr(multiaddr.StringCast(`/ip4/127.0.0.1/tcp/32954/p2p/12D3KooWJwW8pUpTkxPTMv84RPLPMQVEAjZ6fvJuX4oZrvW5DAGQ`))
    24  
    25  	tx1, _ = action.SignedTransfer(identityset.Address(28).String(),
    26  		identityset.PrivateKey(28), 3, big.NewInt(10), []byte{}, testutil.TestGasLimit,
    27  		big.NewInt(testutil.TestGasPriceInt64))
    28  	txProto1 = tx1.Proto()
    29  	tx2, _   = action.SignedExecution(identityset.Address(29).String(),
    30  		identityset.PrivateKey(29), 1, big.NewInt(0), testutil.TestGasLimit,
    31  		big.NewInt(testutil.TestGasPriceInt64), []byte{})
    32  	txProto2  = tx2.Proto()
    33  	_messages = []*Message{
    34  		// broadcast Messages
    35  		{
    36  			// MsgType: iotexrpc.MessageType_ACTIONS,
    37  			ChainID: 1,
    38  			Data:    txProto1,
    39  			Target:  nil,
    40  		},
    41  		{
    42  			ChainID: 3,
    43  			Target:  nil,
    44  			Data:    &iotextypes.Action{},
    45  		},
    46  		{
    47  			// MsgType: iotexrpc.MessageType_ACTIONS,
    48  			ChainID: 2,
    49  			Data:    txProto2,
    50  			Target:  nil,
    51  		},
    52  		{
    53  			ChainID: 4,
    54  			Target:  nil,
    55  			Data:    &iotextypes.Action{},
    56  		},
    57  		// unicast Messages
    58  		{
    59  			// MsgType: iotexrpc.MessageType_ACTIONS,
    60  			ChainID: 1,
    61  			Data:    txProto1,
    62  			Target:  peerAddr1,
    63  		},
    64  		{
    65  			// MsgType: iotexrpc.MessageType_ACTIONS,
    66  			ChainID: 2,
    67  			Data:    txProto2,
    68  			Target:  peerAddr1,
    69  		},
    70  	}
    71  )
    72  
    73  func TestBatchManager(t *testing.T) {
    74  	require := require.New(t)
    75  
    76  	var msgsCount int32 = 0
    77  	callback := func(msg *Message) error {
    78  		atomic.AddInt32(&msgsCount, 1)
    79  		return nil
    80  	}
    81  
    82  	manager := NewManager(callback)
    83  	manager.Start()
    84  	defer manager.Stop()
    85  
    86  	t.Run("msgWithSizelimit", func(t *testing.T) {
    87  		manager.Put(_messages[0], WithSizeLimit(2))
    88  		manager.Put(_messages[0], WithSizeLimit(2))
    89  		manager.Put(_messages[0], WithSizeLimit(2))
    90  		err := testutil.WaitUntil(50*time.Millisecond, 3*time.Second, func() (bool, error) {
    91  			return atomic.LoadInt32(&msgsCount) == 2, nil
    92  		})
    93  		require.NoError(err)
    94  
    95  		manager.Put(_messages[2], WithSizeLimit(2))
    96  		manager.Put(_messages[1], WithSizeLimit(2))
    97  		manager.Put(_messages[2], WithSizeLimit(2))
    98  		manager.Put(_messages[1], WithSizeLimit(2))
    99  		err = testutil.WaitUntil(50*time.Millisecond, 3*time.Second, func() (bool, error) {
   100  			return atomic.LoadInt32(&msgsCount) == 4, nil
   101  		})
   102  		require.NoError(err)
   103  
   104  		manager.Put(_messages[5], WithSizeLimit(1))
   105  		err = testutil.WaitUntil(50*time.Millisecond, 3*time.Second, func() (bool, error) {
   106  			return atomic.LoadInt32(&msgsCount) == 5, nil
   107  		})
   108  		require.NoError(err)
   109  		require.Equal(4, len(manager.writerMap))
   110  	})
   111  
   112  	msgsCount = 0
   113  	manager.writerMap = make(map[batchID]*batchWriter)
   114  
   115  	t.Run("msgWithInterval", func(t *testing.T) {
   116  		manager.Put(_messages[0], WithInterval(50*time.Millisecond))
   117  		time.Sleep(60 * time.Millisecond)
   118  		err := testutil.WaitUntil(50*time.Millisecond, 1*time.Second, func() (bool, error) {
   119  			return atomic.LoadInt32(&msgsCount) == 1, nil
   120  		})
   121  		require.NoError(err)
   122  
   123  		manager.Put(_messages[2], WithInterval(50*time.Millisecond))
   124  		manager.Put(_messages[3], WithInterval(50*time.Millisecond))
   125  		time.Sleep(60 * time.Millisecond)
   126  		err = testutil.WaitUntil(50*time.Millisecond, 1*time.Second, func() (bool, error) {
   127  			return atomic.LoadInt32(&msgsCount) == 3, nil
   128  		})
   129  		require.NoError(err)
   130  	})
   131  
   132  	msgsCount = 0
   133  	manager.writerMap = make(map[batchID]*batchWriter)
   134  
   135  	t.Run("writerTimeout", func(t *testing.T) {
   136  		manager.Put(_messages[0], WithInterval(1*time.Minute))
   137  		manager.Put(_messages[1], WithInterval(1*time.Minute))
   138  		manager.Put(_messages[2], WithInterval(1*time.Minute))
   139  		manager.Put(_messages[3], WithInterval(1*time.Minute))
   140  		require.Equal(4, len(manager.writerMap))
   141  		err := testutil.WaitUntil(50*time.Millisecond, 1*time.Second, func() (bool, error) {
   142  			queuedItems := 0
   143  			for _, writer := range manager.writerMap {
   144  				queuedItems += len(writer.msgBuffer)
   145  			}
   146  
   147  			return queuedItems == 0, nil
   148  		})
   149  		require.NoError(err)
   150  		manager.cleanupLoop()
   151  		manager.cleanupLoop()
   152  		err = testutil.WaitUntil(50*time.Millisecond, 1*time.Second, func() (bool, error) {
   153  			return atomic.LoadInt32(&msgsCount) == 4, nil
   154  		})
   155  		require.NoError(err)
   156  		require.Equal(0, len(manager.writerMap))
   157  	})
   158  }
   159  
   160  func TestBatchDataCorrectness(t *testing.T) {
   161  	require := require.New(t)
   162  
   163  	var (
   164  		msgsCount    int32 = 0
   165  		expectedData       = iotextypes.Actions{Actions: []*iotextypes.Action{txProto1, txProto1}}
   166  	)
   167  	callback := func(msg *Message) error {
   168  		atomic.AddInt32(&msgsCount, 1)
   169  		binary1, _ := proto.Marshal(&expectedData)
   170  		binary2, _ := proto.Marshal(msg.Data)
   171  		require.Equal(binary1, binary2)
   172  		return nil
   173  	}
   174  
   175  	manager := NewManager(callback)
   176  	manager.Start()
   177  	defer manager.Stop()
   178  
   179  	manager.Put(_messages[0], WithSizeLimit(2))
   180  	manager.Put(_messages[0], WithSizeLimit(2))
   181  	err := testutil.WaitUntil(50*time.Millisecond, 3*time.Second, func() (bool, error) {
   182  		return atomic.LoadInt32(&msgsCount) == 1, nil
   183  	})
   184  	require.NoError(err)
   185  }
   186  
   187  func TestBatchWriter(t *testing.T) {
   188  	require := require.New(t)
   189  
   190  	manager := &Manager{
   191  		assembleQueue: make(chan *batch, _bufferLength),
   192  	}
   193  
   194  	var batchesCount int32 = 0
   195  	go func() {
   196  		for range manager.assembleQueue {
   197  			atomic.AddInt32(&batchesCount, 1)
   198  		}
   199  	}()
   200  
   201  	isBatchNil := func(writer *batchWriter) bool {
   202  		writer.mu.Lock()
   203  		ret := writer.curBatch == nil
   204  		writer.mu.Unlock()
   205  		return ret
   206  	}
   207  
   208  	t.Run("msgWithSizelimit", func(t *testing.T) {
   209  		writer := newBatchWriter(
   210  			&writerConfig{
   211  				expiredThreshold: 10,
   212  				sizeLimit:        2,
   213  				msgInterval:      10 * time.Minute,
   214  			},
   215  			manager,
   216  		)
   217  
   218  		// wait until writer is ready
   219  		err := testutil.WaitUntil(10*time.Millisecond, 10*time.Second, func() (bool, error) {
   220  			return writer.IsReady(), nil
   221  		})
   222  		require.NoError(err)
   223  
   224  		writer.Put(_messages[0])
   225  		writer.Put(_messages[1])
   226  		err = testutil.WaitUntil(50*time.Millisecond, 1*time.Second, func() (bool, error) {
   227  			return atomic.LoadInt32(&batchesCount) == 1, nil
   228  		})
   229  		require.NoError(err)
   230  		require.True(isBatchNil(writer))
   231  
   232  		writer.Put(_messages[5])
   233  		err = testutil.WaitUntil(50*time.Millisecond, 1*time.Second, func() (bool, error) {
   234  			return atomic.LoadInt32(&batchesCount) == 1, nil
   235  		})
   236  		require.NoError(err)
   237  		require.False(isBatchNil(writer))
   238  
   239  		writer.Put(_messages[3])
   240  		err = testutil.WaitUntil(50*time.Millisecond, 1*time.Second, func() (bool, error) {
   241  			return atomic.LoadInt32(&batchesCount) == 2, nil
   242  		})
   243  		require.NoError(err)
   244  		require.True(isBatchNil(writer))
   245  
   246  		writer.Put(_messages[3])
   247  		err = testutil.WaitUntil(50*time.Millisecond, 1*time.Second, func() (bool, error) {
   248  			return atomic.LoadInt32(&batchesCount) == 2, nil
   249  		})
   250  		require.NoError(err)
   251  		require.False(isBatchNil(writer))
   252  		writer.Close()
   253  		require.Error(writer.Put(_messages[3]))
   254  		err = testutil.WaitUntil(50*time.Millisecond, 1*time.Second, func() (bool, error) {
   255  			return atomic.LoadInt32(&batchesCount) == 3, nil
   256  		})
   257  		require.NoError(err)
   258  		require.Nil(writer.curBatch)
   259  	})
   260  
   261  	batchesCount = 0
   262  
   263  	t.Run("msgWithInterval", func(t *testing.T) {
   264  		writer := newBatchWriter(
   265  			&writerConfig{
   266  				expiredThreshold: 10,
   267  				sizeLimit:        100,
   268  				msgInterval:      50 * time.Millisecond,
   269  			},
   270  			manager,
   271  		)
   272  
   273  		// wait until writer is ready
   274  		err := testutil.WaitUntil(10*time.Millisecond, 10*time.Second, func() (bool, error) {
   275  			return writer.IsReady(), nil
   276  		})
   277  		require.NoError(err)
   278  
   279  		writer.Put(_messages[0])
   280  		time.Sleep(60 * time.Millisecond)
   281  		err = testutil.WaitUntil(50*time.Millisecond, 1*time.Second, func() (bool, error) {
   282  			return atomic.LoadInt32(&batchesCount) == 1, nil
   283  		})
   284  		require.NoError(err)
   285  		require.True(isBatchNil(writer))
   286  
   287  		writer.Put(_messages[2])
   288  		writer.Put(_messages[3])
   289  		time.Sleep(60 * time.Millisecond)
   290  		err = testutil.WaitUntil(50*time.Millisecond, 1*time.Second, func() (bool, error) {
   291  			return atomic.LoadInt32(&batchesCount) == 2, nil
   292  		})
   293  		require.NoError(err)
   294  		require.True(isBatchNil(writer))
   295  
   296  		writer.Put(_messages[4])
   297  		writer.Close()
   298  		require.Error(writer.Put(_messages[4]))
   299  		time.Sleep(60 * time.Millisecond)
   300  		require.Error(writer.Put(_messages[4]))
   301  		err = testutil.WaitUntil(50*time.Millisecond, 1*time.Second, func() (bool, error) {
   302  			return atomic.LoadInt32(&batchesCount) == 3, nil
   303  		})
   304  		require.NoError(err)
   305  		require.True(isBatchNil(writer))
   306  	})
   307  }
   308  
   309  func TestBatch(t *testing.T) {
   310  	require := require.New(t)
   311  
   312  	readyChan := make(chan struct{})
   313  	var isReady int32 = 0
   314  	go func() {
   315  		for range readyChan {
   316  			atomic.AddInt32(&isReady, 1)
   317  			return
   318  		}
   319  	}()
   320  	batch := &batch{
   321  		msgs:      make([]*Message, 0),
   322  		sizeLimit: 4,
   323  		ready:     readyChan,
   324  	}
   325  
   326  	batch.Add(_messages[0])
   327  	batch.Add(_messages[1])
   328  	require.Equal(2, batch.Size())
   329  	require.False(batch.Full())
   330  
   331  	batch.Add(_messages[2])
   332  	batch.Add(_messages[3])
   333  	require.Equal(4, batch.Size())
   334  	require.True(batch.Full())
   335  
   336  	batch.Flush()
   337  	err := testutil.WaitUntil(50*time.Millisecond, 1*time.Second, func() (bool, error) {
   338  		return atomic.LoadInt32(&isReady) == 1, nil
   339  	})
   340  	require.NoError(err)
   341  }
   342  
   343  func TestMessageBatchID(t *testing.T) {
   344  	require := require.New(t)
   345  
   346  	hashSet := make(map[batchID]struct{})
   347  	for _, msg := range _messages {
   348  		id, err := msg.batchID()
   349  		require.NoError(err)
   350  		hashSet[id] = struct{}{}
   351  	}
   352  	require.Less(0, len(_messages))
   353  	require.Equal(len(_messages), len(hashSet))
   354  }
   355  
   356  func BenchmarkBatchManager(b *testing.B) {
   357  	var (
   358  		numMsgsForTest = 1000
   359  		msgSize        = 500
   360  	)
   361  
   362  	var msgsCount int32 = 0
   363  	callback := func(msg *Message) error {
   364  		atomic.AddInt32(&msgsCount, 1)
   365  		return nil
   366  	}
   367  
   368  	manager := NewManager(callback)
   369  	manager.Start()
   370  	defer manager.Stop()
   371  
   372  	b.Run("packedMsgs", func(b *testing.B) {
   373  		// generate Messages
   374  		messages := make([]*Message, 0, numMsgsForTest)
   375  		chainIDRange := 2
   376  		for i, chainID := 0, 0; i < numMsgsForTest; i++ {
   377  			messages = append(messages, &Message{
   378  				ChainID: uint32(chainID),
   379  				Data:    txProto1,
   380  			})
   381  			chainID = (chainID + 1) % chainIDRange
   382  		}
   383  
   384  		b.ResetTimer()
   385  		for n := 0; n < b.N; n++ {
   386  			manager.writerMap = make(map[batchID]*batchWriter) // reset batch factory manually
   387  			for i := range messages {
   388  				manager.Put(messages[i], WithSizeLimit(10000))
   389  			}
   390  		}
   391  		b.StopTimer()
   392  	})
   393  
   394  	runtime.GC()
   395  
   396  	b.Run("multipleBatchWriters", func(b *testing.B) {
   397  		// generate Messages
   398  		messages := make([]*Message, 0, numMsgsForTest)
   399  		for i := 0; i < numMsgsForTest; i++ {
   400  			messages = append(messages, &Message{
   401  				ChainID: uint32(i),
   402  				Data:    txProto1,
   403  			})
   404  		}
   405  
   406  		b.ResetTimer()
   407  		for n := 0; n < b.N; n++ {
   408  			manager.writerMap = make(map[batchID]*batchWriter) // reset batch factory manually
   409  			for i := range messages {
   410  				manager.Put(messages[i], WithSizeLimit(uint64(msgSize)))
   411  			}
   412  		}
   413  		b.StopTimer()
   414  	})
   415  }