github.com/angenalZZZ/gofunc@v0.0.0-20210507121333-48ff1be3917b/f/timer.go (about)

     1  package f
     2  
     3  import (
     4  	"container/heap"
     5  	"container/list"
     6  	"errors"
     7  	"sync"
     8  	"sync/atomic"
     9  	"time"
    10  	"unsafe"
    11  )
    12  
    13  // Timer is an implementation of Hierarchical Timing Wheels.
    14  type Timer struct {
    15  	tick      int64 // in milliseconds
    16  	wheelSize int64
    17  
    18  	interval    int64 // in milliseconds
    19  	currentTime int64 // in milliseconds
    20  	buckets     []*TimerBucket
    21  	queue       *timerDelayQueue
    22  
    23  	// The higher-level overflow wheel.
    24  	//
    25  	// NOTE: This field may be updated and read concurrently, through Add().
    26  	overflowWheel unsafe.Pointer // type: *Timer
    27  
    28  	exitC     chan struct{}
    29  	waitGroup sync.WaitGroup
    30  }
    31  
    32  // NewTimer creates an instance of Timer with the given tick and wheelSize.
    33  func NewTimer(tick time.Duration, wheelSize int64) *Timer {
    34  	tickMs := int64(tick / time.Millisecond)
    35  	if tickMs <= 0 {
    36  		panic(errors.New("tick must be greater than or equal to 1ms"))
    37  	}
    38  
    39  	startMs := time.Now().UTC().UnixNano() / int64(time.Millisecond)
    40  
    41  	return newTimer(
    42  		tickMs,
    43  		wheelSize,
    44  		startMs,
    45  		newTimerDelayQueue(int(wheelSize)),
    46  	)
    47  }
    48  
    49  // newTimer is an internal helper function that really creates an instance of Timer.
    50  func newTimer(tickMs int64, wheelSize int64, startMs int64, queue *timerDelayQueue) *Timer {
    51  	currentTime := startMs
    52  	if tickMs > 0 {
    53  		currentTime = startMs - startMs%tickMs
    54  	}
    55  	buckets := make([]*TimerBucket, wheelSize)
    56  	for i := range buckets {
    57  		buckets[i] = NewTimerBucket()
    58  	}
    59  	return &Timer{
    60  		tick:        tickMs,
    61  		wheelSize:   wheelSize,
    62  		currentTime: currentTime,
    63  		interval:    tickMs * wheelSize,
    64  		buckets:     buckets,
    65  		queue:       queue,
    66  		exitC:       make(chan struct{}),
    67  	}
    68  }
    69  
    70  // add inserts the timer t into the current timing wheel.
    71  func (tw *Timer) add(t *TimerElement) bool {
    72  	currentTime := atomic.LoadInt64(&tw.currentTime)
    73  	if t.expiration < currentTime+tw.tick {
    74  		// Already expired
    75  		return false
    76  	} else if t.expiration < currentTime+tw.interval {
    77  		// Put it into its own bucket
    78  		virtualID := t.expiration / tw.tick
    79  		b := tw.buckets[virtualID%tw.wheelSize]
    80  		b.Add(t)
    81  
    82  		// Set the bucket expiration time
    83  		if b.SetExpiration(virtualID * tw.tick) {
    84  			// The bucket needs to be enqueued since it was an expired bucket.
    85  			// We only need to enqueue the bucket when its expiration time has changed,
    86  			// i.e. the wheel has advanced and this bucket get reused with a new expiration.
    87  			// Any further calls to set the expiration within the same wheel cycle will
    88  			// pass in the same value and hence return false, thus the bucket with the
    89  			// same expiration will not be enqueued multiple times.
    90  			tw.queue.Offer(b, b.Expiration())
    91  		}
    92  		return true
    93  	} else {
    94  		// Out of the interval. Put it into the overflow wheel
    95  		overflowWheel := atomic.LoadPointer(&tw.overflowWheel)
    96  		if overflowWheel == nil {
    97  			atomic.CompareAndSwapPointer(
    98  				&tw.overflowWheel,
    99  				nil,
   100  				unsafe.Pointer(newTimer(
   101  					tw.interval,
   102  					tw.wheelSize,
   103  					currentTime,
   104  					tw.queue,
   105  				)),
   106  			)
   107  			overflowWheel = atomic.LoadPointer(&tw.overflowWheel)
   108  		}
   109  		return (*Timer)(overflowWheel).add(t)
   110  	}
   111  }
   112  
   113  // addOrRun inserts the timer t into the current timing wheel, or run the
   114  // timer's task if it has already expired.
   115  func (tw *Timer) addOrRun(t *TimerElement) {
   116  	if !tw.add(t) {
   117  		// Already expired
   118  
   119  		// Like the standard time.AfterFunc (https://golang.org/pkg/time/#AfterFunc),
   120  		// always execute the timer's task in its own goroutine.
   121  		go t.task()
   122  	}
   123  }
   124  
   125  func (tw *Timer) advanceClock(expiration int64) {
   126  	currentTime := atomic.LoadInt64(&tw.currentTime)
   127  	if expiration >= currentTime+tw.tick {
   128  		currentTime := expiration
   129  		if tw.tick > 0 {
   130  			currentTime = expiration - expiration%tw.tick
   131  		}
   132  		atomic.StoreInt64(&tw.currentTime, currentTime)
   133  
   134  		// Try to advance the clock of the overflow wheel if present
   135  		overflowWheel := atomic.LoadPointer(&tw.overflowWheel)
   136  		if overflowWheel != nil {
   137  			(*Timer)(overflowWheel).advanceClock(currentTime)
   138  		}
   139  	}
   140  }
   141  
   142  // Start starts the current timing wheel.
   143  func (tw *Timer) Start() {
   144  	tw.waitGroup.Add(1)
   145  	go func() {
   146  		defer tw.waitGroup.Done()
   147  		tw.queue.Poll(tw.exitC, func() int64 {
   148  			return time.Now().UTC().UnixNano() / int64(time.Millisecond)
   149  		})
   150  	}()
   151  
   152  	tw.waitGroup.Add(1)
   153  	go func() {
   154  		defer tw.waitGroup.Done()
   155  		for {
   156  			select {
   157  			case elem := <-tw.queue.C:
   158  				b := elem.(*TimerBucket)
   159  				tw.advanceClock(b.Expiration())
   160  				b.Flush(tw.addOrRun)
   161  			case <-tw.exitC:
   162  				return
   163  			}
   164  		}
   165  	}()
   166  }
   167  
   168  // Stop stops the current timing wheel.
   169  //
   170  // If there is any timer's task being running in its own goroutine, Stop does
   171  // not wait for the task to complete before returning. If the caller needs to
   172  // know whether the task is completed, it must coordinate with the task explicitly.
   173  func (tw *Timer) Stop() {
   174  	close(tw.exitC)
   175  	tw.waitGroup.Wait()
   176  }
   177  
   178  // AfterFunc waits for the duration to elapse and then calls f in its own goroutine.
   179  // It returns a Timer that can be used to cancel the call using its Stop method.
   180  func (tw *Timer) AfterFunc(d time.Duration, f func()) *TimerElement {
   181  	t := &TimerElement{
   182  		expiration: time.Now().UTC().Add(d).UnixNano() / int64(time.Millisecond),
   183  		task:       f,
   184  	}
   185  	tw.addOrRun(t)
   186  	return t
   187  }
   188  
   189  // TimerScheduler determines the execution plan of a task.
   190  type TimerScheduler interface {
   191  	// Next returns the next execution time after the given (previous) time.
   192  	// It will return a zero time if no next time is scheduled.
   193  	//
   194  	// All times must be UTC.
   195  	Next(time.Time) time.Time
   196  }
   197  
   198  // ScheduleFunc calls f (in its own goroutine) according to the execution
   199  // plan scheduled by s. It returns a Timer that can be used to cancel the
   200  // call using its Stop method.
   201  //
   202  // If the caller want to terminate the execution plan halfway, it must
   203  // stop the timer and ensure that the timer is stopped actually, since in
   204  // the current implementation, there is a gap between the expiring and the
   205  // restarting of the timer. The wait time for ensuring is short since the
   206  // gap is very small.
   207  //
   208  // Internally, ScheduleFunc will ask the first execution time (by calling
   209  // s.Next()) initially, and create a timer if the execution time is non-zero.
   210  // Afterwards, it will ask the next execution time each time f is about to
   211  // be executed, and f will be called at the next execution time if the time
   212  // is non-zero.
   213  func (tw *Timer) ScheduleFunc(s TimerScheduler, f func()) (t *TimerElement) {
   214  	expiration := s.Next(time.Now().UTC())
   215  	if expiration.IsZero() {
   216  		// No time is scheduled, return nil.
   217  		return
   218  	}
   219  
   220  	t = &TimerElement{
   221  		expiration: expiration.UnixNano() / int64(time.Millisecond),
   222  		task: func() {
   223  			// Schedule the task to execute at the next time if possible.
   224  			expiration := s.Next(time.Unix(0, t.expiration*int64(time.Millisecond)).UTC())
   225  			if !expiration.IsZero() {
   226  				t.expiration = expiration.UnixNano() / int64(time.Millisecond)
   227  				tw.addOrRun(t)
   228  			}
   229  
   230  			// Actually execute the task.
   231  			f()
   232  		},
   233  	}
   234  	tw.addOrRun(t)
   235  
   236  	return
   237  }
   238  
   239  // The start of TimerElement implementation.
   240  
   241  // TimerElement represents a single event. When the Timer expires, the given
   242  // task will be executed.
   243  type TimerElement struct {
   244  	expiration int64 // in milliseconds
   245  	task       func()
   246  
   247  	// The bucket that holds the list to which this timer's element belongs.
   248  	//
   249  	// NOTE: This field may be updated and read concurrently,
   250  	// through Timer.Stop() and Bucket.Flush().
   251  	b unsafe.Pointer // type: *bucket
   252  
   253  	// The timer's element.
   254  	element *list.Element
   255  }
   256  
   257  func (t *TimerElement) getBucket() *TimerBucket {
   258  	return (*TimerBucket)(atomic.LoadPointer(&t.b))
   259  }
   260  
   261  func (t *TimerElement) setBucket(b *TimerBucket) {
   262  	atomic.StorePointer(&t.b, unsafe.Pointer(b))
   263  }
   264  
   265  // Stop prevents the Timer from firing. It returns true if the call
   266  // stops the timer, false if the timer has already expired or been stopped.
   267  //
   268  // If the timer t has already expired and the t.task has been started in its own
   269  // goroutine; Stop does not wait for t.task to complete before returning. If the caller
   270  // needs to know whether t.task is completed, it must coordinate with t.task explicitly.
   271  func (t *TimerElement) Stop() bool {
   272  	stopped := false
   273  	for b := t.getBucket(); b != nil; b = t.getBucket() {
   274  		// If b.Remove is called just after the timing wheel's goroutine has:
   275  		//     1. removed t from b (through b.Flush -> b.remove)
   276  		//     2. moved t from b to another bucket ab (through b.Flush -> b.remove and ab.Add)
   277  		// this may fail to remove t due to the change of t's bucket.
   278  		stopped = b.Remove(t)
   279  
   280  		// Thus, here we re-get t's possibly new bucket (nil for case 1, or ab (non-nil) for case 2),
   281  		// and retry until the bucket becomes nil, which indicates that t has finally been removed.
   282  	}
   283  	return stopped
   284  }
   285  
   286  // TimerBucket manage timers list
   287  type TimerBucket struct {
   288  	// 64-bit atomic operations require 64-bit alignment, but 32-bit
   289  	// compilers do not ensure it. So we must keep the 64-bit field
   290  	// as the first field of the struct.
   291  	//
   292  	// For more explanations, see https://golang.org/pkg/sync/atomic/#pkg-note-BUG
   293  	expiration int64
   294  
   295  	mu     sync.Mutex
   296  	Timers *list.List
   297  }
   298  
   299  // NewTimerBucket creates timers list
   300  func NewTimerBucket() *TimerBucket {
   301  	return &TimerBucket{
   302  		Timers:     list.New(),
   303  		expiration: -1,
   304  	}
   305  }
   306  
   307  // Expiration get expiration time
   308  func (b *TimerBucket) Expiration() int64 {
   309  	return atomic.LoadInt64(&b.expiration)
   310  }
   311  
   312  // SetExpiration set expiration time
   313  func (b *TimerBucket) SetExpiration(expiration int64) bool {
   314  	return atomic.SwapInt64(&b.expiration, expiration) != expiration
   315  }
   316  
   317  // Add a element to timers list
   318  func (b *TimerBucket) Add(t *TimerElement) {
   319  	b.mu.Lock()
   320  
   321  	e := b.Timers.PushBack(t)
   322  	t.setBucket(b)
   323  	t.element = e
   324  
   325  	b.mu.Unlock()
   326  }
   327  
   328  // Remove a element to timers list
   329  func (b *TimerBucket) Remove(t *TimerElement) bool {
   330  	b.mu.Lock()
   331  	defer b.mu.Unlock()
   332  	return b.remove(t)
   333  }
   334  
   335  func (b *TimerBucket) remove(t *TimerElement) bool {
   336  	if t.getBucket() != b {
   337  		// If remove is called from t.Stop, and this happens just after the timing wheel's goroutine has:
   338  		//     1. removed t from b (through b.Flush -> b.remove)
   339  		//     2. moved t from b to another bucket ab (through b.Flush -> b.remove and ab.Add)
   340  		// then t.getBucket will return nil for case 1, or ab (non-nil) for case 2.
   341  		// In either case, the returned value does not equal to b.
   342  		return false
   343  	}
   344  	b.Timers.Remove(t.element)
   345  	t.setBucket(nil)
   346  	t.element = nil
   347  	return true
   348  }
   349  
   350  // Flush timers list
   351  func (b *TimerBucket) Flush(reinsert func(*TimerElement)) {
   352  	var ts []*TimerElement
   353  
   354  	b.mu.Lock()
   355  	for e := b.Timers.Front(); e != nil; {
   356  		next := e.Next()
   357  
   358  		t := e.Value.(*TimerElement)
   359  		b.remove(t)
   360  		ts = append(ts, t)
   361  
   362  		e = next
   363  	}
   364  	b.mu.Unlock()
   365  
   366  	b.SetExpiration(-1)
   367  
   368  	for _, t := range ts {
   369  		reinsert(t)
   370  	}
   371  }
   372  
   373  // The end of TimerElement implementation.
   374  
   375  // The start of PriorityQueue implementation.
   376  // Borrowed from https://github.com/nsqio/nsq/blob/master/internal/pqueue/pqueue.go
   377  
   378  type priorityQueueItem struct {
   379  	Value    interface{}
   380  	Priority int64
   381  	Index    int
   382  }
   383  
   384  // this is a priority queue as implemented by a min heap
   385  // ie. the 0th element is the *lowest* value
   386  type priorityQueue []*priorityQueueItem
   387  
   388  func newPriorityQueue(capacity int) priorityQueue {
   389  	return make(priorityQueue, 0, capacity)
   390  }
   391  
   392  func (pq priorityQueue) Len() int {
   393  	return len(pq)
   394  }
   395  
   396  func (pq priorityQueue) Less(i, j int) bool {
   397  	return pq[i].Priority < pq[j].Priority
   398  }
   399  
   400  func (pq priorityQueue) Swap(i, j int) {
   401  	pq[i], pq[j] = pq[j], pq[i]
   402  	pq[i].Index = i
   403  	pq[j].Index = j
   404  }
   405  
   406  func (pq *priorityQueue) Push(x interface{}) {
   407  	n := len(*pq)
   408  	c := cap(*pq)
   409  	if n+1 > c {
   410  		npq := make(priorityQueue, n, c*2)
   411  		copy(npq, *pq)
   412  		*pq = npq
   413  	}
   414  	*pq = (*pq)[0 : n+1]
   415  	item := x.(*priorityQueueItem)
   416  	item.Index = n
   417  	(*pq)[n] = item
   418  }
   419  
   420  func (pq *priorityQueue) Pop() interface{} {
   421  	n := len(*pq)
   422  	c := cap(*pq)
   423  	if n < (c/2) && c > 25 {
   424  		npq := make(priorityQueue, n, c/2)
   425  		copy(npq, *pq)
   426  		*pq = npq
   427  	}
   428  	item := (*pq)[n-1]
   429  	item.Index = -1
   430  	*pq = (*pq)[0 : n-1]
   431  	return item
   432  }
   433  
   434  func (pq *priorityQueue) PeekAndShift(max int64) (*priorityQueueItem, int64) {
   435  	if pq.Len() == 0 {
   436  		return nil, 0
   437  	}
   438  
   439  	item := (*pq)[0]
   440  	if item.Priority > max {
   441  		return nil, item.Priority - max
   442  	}
   443  	heap.Remove(pq, 0)
   444  
   445  	return item, 0
   446  }
   447  
   448  // The end of PriorityQueue implementation.
   449  
   450  // timerDelayQueue is an unbounded blocking queue of *Delayed* elements, in which
   451  // an element can only be taken when its delay has expired. The head of the
   452  // queue is the *Delayed* element whose delay expired furthest in the past.
   453  type timerDelayQueue struct {
   454  	C chan interface{}
   455  
   456  	mu sync.Mutex
   457  	pq priorityQueue
   458  
   459  	// Similar to the sleeping state of runtime.Timers.
   460  	sleeping int32
   461  	wakeupC  chan struct{}
   462  }
   463  
   464  // newTimerDelayQueue creates an instance of delayQueue with the specified size.
   465  func newTimerDelayQueue(size int) *timerDelayQueue {
   466  	return &timerDelayQueue{
   467  		C:       make(chan interface{}),
   468  		pq:      newPriorityQueue(size),
   469  		wakeupC: make(chan struct{}),
   470  	}
   471  }
   472  
   473  // Offer inserts the element into the current queue.
   474  func (dq *timerDelayQueue) Offer(elem interface{}, expiration int64) {
   475  	item := &priorityQueueItem{Value: elem, Priority: expiration}
   476  
   477  	dq.mu.Lock()
   478  	heap.Push(&dq.pq, item)
   479  	index := item.Index
   480  	dq.mu.Unlock()
   481  
   482  	if index == 0 {
   483  		// A new item with the earliest expiration is added.
   484  		if atomic.CompareAndSwapInt32(&dq.sleeping, 1, 0) {
   485  			dq.wakeupC <- struct{}{}
   486  		}
   487  	}
   488  }
   489  
   490  // Poll starts an infinite loop, in which it continually waits for an element
   491  // to expire and then send the expired element to the channel C.
   492  func (dq *timerDelayQueue) Poll(exitC chan struct{}, nowF func() int64) {
   493  	for {
   494  		now := nowF()
   495  
   496  		dq.mu.Lock()
   497  		item, delta := dq.pq.PeekAndShift(now)
   498  		if item == nil {
   499  			// No items left or at least one item is pending.
   500  
   501  			// We must ensure the atomicity of the whole operation, which is
   502  			// composed of the above PeekAndShift and the following StoreInt32,
   503  			// to avoid possible race conditions between Offer and Poll.
   504  			atomic.StoreInt32(&dq.sleeping, 1)
   505  		}
   506  		dq.mu.Unlock()
   507  
   508  		if item == nil {
   509  			if delta == 0 {
   510  				// No items left.
   511  				select {
   512  				case <-dq.wakeupC:
   513  					// Wait until a new item is added.
   514  					continue
   515  				case <-exitC:
   516  					goto exit
   517  				}
   518  			} else if delta > 0 {
   519  				// At least one item is pending.
   520  				select {
   521  				case <-dq.wakeupC:
   522  					// A new item with an "earlier" expiration than the current "earliest" one is added.
   523  					continue
   524  				case <-time.After(time.Duration(delta) * time.Millisecond):
   525  					// The current "earliest" item expires.
   526  
   527  					// Reset the sleeping state since there's no need to receive from wakeupC.
   528  					if atomic.SwapInt32(&dq.sleeping, 0) == 0 {
   529  						// A caller of Offer() is being blocked on sending to wakeupC,
   530  						// drain wakeupC to unblock the caller.
   531  						<-dq.wakeupC
   532  					}
   533  					continue
   534  				case <-exitC:
   535  					goto exit
   536  				}
   537  			}
   538  		}
   539  
   540  		select {
   541  		case dq.C <- item.Value:
   542  			// The expired element has been sent out successfully.
   543  		case <-exitC:
   544  			goto exit
   545  		}
   546  	}
   547  
   548  exit:
   549  	// Reset the states
   550  	atomic.StoreInt32(&dq.sleeping, 0)
   551  }