github.com/weedge/lib@v0.0.0-20230424045628-a36dcc1d90e4/container/queue/delay_queue.go (about)

     1  // from: https://github.com/RussellLuo/timingwheel/blob/master/delayqueue/delayqueue.go
     2  package queue
     3  
     4  import (
     5  	"container/heap"
     6  	"sync"
     7  	"sync/atomic"
     8  	"time"
     9  )
    10  
    11  // DelayQueue is an unbounded blocking queue of *Delayed* elements, in which
    12  // an element can only be taken when its delay has expired. The head of the
    13  // queue is the *Delayed* element whose delay expired furthest in the past.
    14  type DelayQueue struct {
    15  	C chan interface{}
    16  
    17  	mu sync.Mutex
    18  	pq PriorityQueue
    19  
    20  	// Similar to the sleeping state of runtime.timers.
    21  	sleeping int32
    22  	wakeupC  chan struct{}
    23  }
    24  
    25  // New creates an instance of delayQueue with the specified size.
    26  func NewDelayQueue(size int) *DelayQueue {
    27  	return &DelayQueue{
    28  		C:       make(chan interface{}),
    29  		pq:      NewPriorityQueue(size),
    30  		wakeupC: make(chan struct{}),
    31  	}
    32  }
    33  
    34  // Offer inserts the element into the current queue.
    35  func (dq *DelayQueue) Offer(elem interface{}, expiration int64) {
    36  	item := &Item{Value: elem, Priority: expiration}
    37  
    38  	dq.mu.Lock()
    39  	heap.Push(&dq.pq, item)
    40  	index := item.Index
    41  	dq.mu.Unlock()
    42  
    43  	if index == 0 {
    44  		// A new item with the earliest expiration is added.
    45  		if atomic.CompareAndSwapInt32(&dq.sleeping, 1, 0) {
    46  			dq.wakeupC <- struct{}{}
    47  		}
    48  	}
    49  }
    50  
    51  // Poll starts an infinite loop, in which it continually waits for an element
    52  // to expire and then send the expired element to the channel C.
    53  func (dq *DelayQueue) Poll(exitC chan struct{}, nowF func() int64) {
    54  	for {
    55  		now := nowF()
    56  
    57  		dq.mu.Lock()
    58  		item, delta := dq.pq.PeekAndShift(now)
    59  		if item == nil {
    60  			// No items left or at least one item is pending.
    61  
    62  			// We must ensure the atomicity of the whole operation, which is
    63  			// composed of the above PeekAndShift and the following StoreInt32,
    64  			// to avoid possible race conditions between Offer and Poll.
    65  			atomic.StoreInt32(&dq.sleeping, 1)
    66  		}
    67  		dq.mu.Unlock()
    68  
    69  		if item == nil {
    70  			if delta == 0 {
    71  				// No items left.
    72  				select {
    73  				case <-dq.wakeupC:
    74  					// Wait until a new item is added.
    75  					continue
    76  				case <-exitC:
    77  					goto exit
    78  				}
    79  			} else if delta > 0 {
    80  				// At least one item is pending.
    81  				select {
    82  				case <-dq.wakeupC:
    83  					// A new item with an "earlier" expiration than the current "earliest" one is added.
    84  					continue
    85  				case <-time.After(time.Duration(delta) * time.Millisecond): // delay unit ms
    86  					// The current "earliest" item expires.
    87  
    88  					// Reset the sleeping state since there's no need to receive from wakeupC.
    89  					if atomic.SwapInt32(&dq.sleeping, 0) == 0 {
    90  						// A caller of Offer() is being blocked on sending to wakeupC,
    91  						// drain wakeupC to unblock the caller.
    92  						<-dq.wakeupC
    93  					}
    94  					continue
    95  				case <-exitC:
    96  					goto exit
    97  				}
    98  			}
    99  		}
   100  
   101  		select {
   102  		case dq.C <- item.Value:
   103  			// The expired element has been sent out successfully.
   104  		case <-exitC:
   105  			goto exit
   106  		}
   107  	}
   108  
   109  exit:
   110  	// Reset the states
   111  	atomic.StoreInt32(&dq.sleeping, 0)
   112  	//println("exit dq Poll")
   113  }
   114  
   115  // from delay queue ch get elem to do
   116  func (dq *DelayQueue) Do(exitC chan struct{}, do func(interface{})) {
   117  	for {
   118  		select {
   119  		case elem := <-dq.C:
   120  			do(elem)
   121  		case <-exitC:
   122  			//println("exit dq Do")
   123  			return
   124  		}
   125  	}
   126  }