github.com/decred/dcrlnd@v0.7.6/queue/queue.go (about) 1 package queue 2 3 import ( 4 "container/list" 5 "sync" 6 ) 7 8 // ConcurrentQueue is a concurrent-safe FIFO queue with unbounded capacity. 9 // Clients interact with the queue by pushing items into the in channel and 10 // popping items from the out channel. There is a goroutine that manages moving 11 // items from the in channel to the out channel in the correct order that must 12 // be started by calling Start(). 13 type ConcurrentQueue struct { 14 started sync.Once 15 stopped sync.Once 16 17 chanIn chan interface{} 18 chanOut chan interface{} 19 overflow *list.List 20 21 wg sync.WaitGroup 22 quit chan struct{} 23 } 24 25 // NewConcurrentQueue constructs a ConcurrentQueue. The bufferSize parameter is 26 // the capacity of the output channel. When the size of the queue is below this 27 // threshold, pushes do not incur the overhead of the less efficient overflow 28 // structure. 29 func NewConcurrentQueue(bufferSize int) *ConcurrentQueue { 30 return &ConcurrentQueue{ 31 chanIn: make(chan interface{}), 32 chanOut: make(chan interface{}, bufferSize), 33 overflow: list.New(), 34 quit: make(chan struct{}), 35 } 36 } 37 38 // ChanIn returns a channel that can be used to push new items into the queue. 39 func (cq *ConcurrentQueue) ChanIn() chan<- interface{} { 40 return cq.chanIn 41 } 42 43 // ChanOut returns a channel that can be used to pop items from the queue. 44 func (cq *ConcurrentQueue) ChanOut() <-chan interface{} { 45 return cq.chanOut 46 } 47 48 // Start begins a goroutine that manages moving items from the in channel to the 49 // out channel. The queue tries to move items directly to the out channel 50 // minimize overhead, but if the out channel is full it pushes items to an 51 // overflow queue. This must be called before using the queue. 52 func (cq *ConcurrentQueue) Start() { 53 cq.started.Do(cq.start) 54 } 55 56 func (cq *ConcurrentQueue) start() { 57 cq.wg.Add(1) 58 go func() { 59 defer cq.wg.Done() 60 61 readLoop: 62 for { 63 nextElement := cq.overflow.Front() 64 if nextElement == nil { 65 // Overflow queue is empty so incoming items can be pushed 66 // directly to the output channel. If output channel is full 67 // though, push to overflow. 68 select { 69 case item, ok := <-cq.chanIn: 70 if !ok { 71 break readLoop 72 } 73 select { 74 case cq.chanOut <- item: 75 // Optimistically push directly to chanOut 76 default: 77 cq.overflow.PushBack(item) 78 } 79 case <-cq.quit: 80 return 81 } 82 } else { 83 // Overflow queue is not empty, so any new items get pushed to 84 // the back to preserve order. 85 select { 86 case item, ok := <-cq.chanIn: 87 if !ok { 88 break readLoop 89 } 90 cq.overflow.PushBack(item) 91 case cq.chanOut <- nextElement.Value: 92 cq.overflow.Remove(nextElement) 93 case <-cq.quit: 94 return 95 } 96 } 97 } 98 99 // Incoming channel has been closed. Empty overflow queue into 100 // the outgoing channel. 101 nextElement := cq.overflow.Front() 102 for nextElement != nil { 103 select { 104 case cq.chanOut <- nextElement.Value: 105 cq.overflow.Remove(nextElement) 106 case <-cq.quit: 107 return 108 } 109 nextElement = cq.overflow.Front() 110 } 111 112 // Close outgoing channel. 113 close(cq.chanOut) 114 }() 115 } 116 117 // Stop ends the goroutine that moves items from the in channel to the out 118 // channel. This does not clear the queue state, so the queue can be restarted 119 // without dropping items. 120 func (cq *ConcurrentQueue) Stop() { 121 cq.stopped.Do(func() { 122 close(cq.quit) 123 cq.wg.Wait() 124 }) 125 }