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 }