github.com/iotexproject/iotex-core@v1.14.1-rc1/blocksync/buffer.go (about) 1 // Copyright (c) 2019 IoTeX Foundation 2 // This source code is provided 'as is' and no warranties are given as to title or non-infringement, merchantability 3 // or fitness for purpose and, to the extent permitted by law, all liability for your use of the code is disclaimed. 4 // This source code is governed by Apache License 2.0 that can be found in the LICENSE file. 5 6 package blocksync 7 8 import ( 9 "sync" 10 11 "go.uber.org/zap" 12 13 "github.com/iotexproject/iotex-core/pkg/log" 14 ) 15 16 // blockBuffer is used to keep in-coming block in order. 17 type blockBuffer struct { 18 mu sync.RWMutex 19 blockQueues map[uint64]*uniQueue 20 bufferSize uint64 21 intervalSize uint64 22 } 23 24 type syncBlocksInterval struct { 25 Start uint64 26 End uint64 27 } 28 29 func newBlockBuffer(bufferSize, intervalSize uint64) *blockBuffer { 30 return &blockBuffer{ 31 blockQueues: map[uint64]*uniQueue{}, 32 bufferSize: bufferSize, 33 intervalSize: intervalSize, 34 } 35 } 36 37 func (b *blockBuffer) Pop(height uint64) []*peerBlock { 38 b.mu.Lock() 39 defer b.mu.Unlock() 40 queue, ok := b.blockQueues[height] 41 if !ok { 42 return nil 43 } 44 blks := queue.dequeAll() 45 delete(b.blockQueues, height) 46 47 return blks 48 } 49 50 func (b *blockBuffer) Cleanup(height uint64) { 51 b.mu.Lock() 52 defer b.mu.Unlock() 53 54 size := len(b.blockQueues) 55 if size > int(b.bufferSize)*2 { 56 log.L().Warn("blockBuffer is leaking memory.", zap.Int("bufferSize", size)) 57 newQueues := map[uint64]*uniQueue{} 58 for h := range b.blockQueues { 59 if h > height { 60 newQueues[h] = b.blockQueues[h] 61 } 62 } 63 b.blockQueues = newQueues 64 } 65 } 66 67 // AddBlock tries to put given block into buffer and flush buffer into blockchain. 68 func (b *blockBuffer) AddBlock(tipHeight uint64, blk *peerBlock) (bool, uint64) { 69 b.mu.Lock() 70 defer b.mu.Unlock() 71 blkHeight := blk.block.Height() 72 if blkHeight <= tipHeight { 73 return false, blkHeight 74 } 75 if blkHeight > tipHeight+b.bufferSize { 76 return false, tipHeight + b.bufferSize 77 } 78 if _, ok := b.blockQueues[blkHeight]; !ok { 79 b.blockQueues[blkHeight] = newUniQueue() 80 } 81 b.blockQueues[blkHeight].enque(blk) 82 return true, blkHeight 83 } 84 85 // GetBlocksIntervalsToSync returns groups of syncBlocksInterval are missing upto targetHeight. 86 func (b *blockBuffer) GetBlocksIntervalsToSync(confirmedHeight uint64, targetHeight uint64) []syncBlocksInterval { 87 b.mu.RLock() 88 defer b.mu.RUnlock() 89 var ( 90 start uint64 91 startSet bool 92 bi []syncBlocksInterval 93 ) 94 95 // The sync range shouldn't go beyond tip height + buffer size to avoid being too aggressive 96 if targetHeight > confirmedHeight+b.bufferSize { 97 targetHeight = confirmedHeight + b.bufferSize 98 } 99 // The sync range should at least contain one interval to speculatively fetch missing blocks 100 if targetHeight < confirmedHeight+b.intervalSize { 101 targetHeight = confirmedHeight + b.intervalSize 102 } 103 104 var iLen uint64 105 for h := confirmedHeight + 1; h <= targetHeight; h++ { 106 if _, ok := b.blockQueues[h]; !ok { 107 iLen++ 108 if !startSet { 109 start = h 110 startSet = true 111 } 112 if iLen >= b.intervalSize { 113 bi = append(bi, syncBlocksInterval{Start: start, End: h}) 114 startSet = false 115 iLen = 0 116 } 117 continue 118 } 119 if startSet { 120 bi = append(bi, syncBlocksInterval{Start: start, End: h - 1}) 121 startSet = false 122 iLen = 0 123 } 124 } 125 126 // handle last interval 127 if startSet { 128 bi = append(bi, syncBlocksInterval{Start: start, End: targetHeight}) 129 } 130 return bi 131 }