github.com/decred/dcrlnd@v0.7.6/routing/chainview/queue.go (about) 1 package chainview 2 3 import "sync" 4 5 // blockEventType is the possible types of a blockEvent. 6 type blockEventType uint8 7 8 const ( 9 // connected is the type of a blockEvent representing a block 10 // that was connected to our current chain. 11 connected blockEventType = iota 12 13 // disconnected is the type of a blockEvent representing a 14 // block that is stale/disconnected from our current chain. 15 disconnected 16 ) 17 18 // blockEvent represent a block that was either connected 19 // or disconnected from the current chain. 20 type blockEvent struct { 21 eventType blockEventType 22 block *FilteredBlock 23 } 24 25 // blockEventQueue is an ordered queue for block events sent from a 26 // FilteredChainView. The two types of possible block events are 27 // connected/new blocks, and disconnected/stale blocks. The 28 // blockEventQueue keeps the order of these events intact, while 29 // still being non-blocking. This is important in order for the 30 // chainView's call to onBlockConnected/onBlockDisconnected to not 31 // get blocked, and for the consumer of the block events to always 32 // get the events in the correct order. 33 type blockEventQueue struct { 34 queueCond *sync.Cond 35 queueMtx sync.Mutex 36 queue []*blockEvent 37 38 // newBlocks is the channel where the consumer of the queue 39 // will receive connected/new blocks from the FilteredChainView. 40 newBlocks chan *FilteredBlock 41 42 // staleBlocks is the channel where the consumer of the queue will 43 // receive disconnected/stale blocks from the FilteredChainView. 44 staleBlocks chan *FilteredBlock 45 46 wg sync.WaitGroup 47 quit chan struct{} 48 } 49 50 // newBlockEventQueue creates a new blockEventQueue. 51 func newBlockEventQueue() *blockEventQueue { 52 b := &blockEventQueue{ 53 newBlocks: make(chan *FilteredBlock), 54 staleBlocks: make(chan *FilteredBlock), 55 quit: make(chan struct{}), 56 } 57 b.queueCond = sync.NewCond(&b.queueMtx) 58 59 return b 60 } 61 62 // Start starts the blockEventQueue coordinator such that it can start handling 63 // events. 64 func (b *blockEventQueue) Start() { 65 b.wg.Add(1) 66 go b.queueCoordinator() 67 } 68 69 // Stop signals the queue coordinator to stop, such that the queue can be 70 // shut down. 71 func (b *blockEventQueue) Stop() { 72 close(b.quit) 73 74 b.queueCond.Signal() 75 } 76 77 // queueCoordinator is the queue's main loop, handling incoming block events 78 // and handing them off to the correct output channel. 79 // 80 // NB: MUST be run as a goroutine from the Start() method. 81 func (b *blockEventQueue) queueCoordinator() { 82 defer b.wg.Done() 83 84 for { 85 // First, we'll check our condition. If the queue of events is 86 // empty, then we'll wait until a new item is added. 87 b.queueCond.L.Lock() 88 for len(b.queue) == 0 { 89 b.queueCond.Wait() 90 91 // If we were woke up in order to exit, then we'll do 92 // so. Otherwise, we'll check the queue for any new 93 // items. 94 select { 95 case <-b.quit: 96 b.queueCond.L.Unlock() 97 return 98 default: 99 } 100 } 101 102 // Grab the first element in the queue, and nil the index to 103 // avoid gc leak. 104 event := b.queue[0] 105 b.queue[0] = nil 106 b.queue = b.queue[1:] 107 b.queueCond.L.Unlock() 108 109 // In the case this is a connected block, we'll send it on the 110 // newBlocks channel. In case it is a disconnected block, we'll 111 // send it on the staleBlocks channel. This send will block 112 // until it is received by the consumer on the other end, making 113 // sure we won't try to send any other block event before the 114 // consumer is aware of this one. 115 switch event.eventType { 116 case connected: 117 select { 118 case b.newBlocks <- event.block: 119 case <-b.quit: 120 return 121 } 122 case disconnected: 123 select { 124 case b.staleBlocks <- event.block: 125 case <-b.quit: 126 return 127 } 128 } 129 } 130 } 131 132 // Add puts the provided blockEvent at the end of the event queue, making sure 133 // it will first be received after all previous events. This method is 134 // non-blocking, in the sense that it will never wait for the consumer of the 135 // queue to read form the other end, making it safe to call from the 136 // FilteredChainView's onBlockConnected/onBlockDisconnected. 137 func (b *blockEventQueue) Add(event *blockEvent) { 138 139 // Lock the condition, and add the event to the end of queue. 140 b.queueCond.L.Lock() 141 b.queue = append(b.queue, event) 142 b.queueCond.L.Unlock() 143 144 // With the event added, we signal to the queueCoordinator that 145 // there are new events to handle. 146 b.queueCond.Signal() 147 }