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  }