github.com/dubbogo/gost@v1.14.0/time/timer.go (about)

     1  /*
     2   * Licensed to the Apache Software Foundation (ASF) under one or more
     3   * contributor license agreements.  See the NOTICE file distributed with
     4   * this work for additional information regarding copyright ownership.
     5   * The ASF licenses this file to You under the Apache License, Version 2.0
     6   * (the "License"); you may not use this file except in compliance with
     7   * the License.  You may obtain a copy of the License at
     8   *
     9   *     http://www.apache.org/licenses/LICENSE-2.0
    10   *
    11   * Unless required by applicable law or agreed to in writing, software
    12   * distributed under the License is distributed on an "AS IS" BASIS,
    13   * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    14   * See the License for the specific language governing permissions and
    15   * limitations under the License.
    16   */
    17  
    18  // Package gxtime encapsulates some golang.time functions
    19  package gxtime
    20  
    21  import (
    22  	"container/list"
    23  	"errors"
    24  	"log"
    25  	"sync"
    26  	"sync/atomic"
    27  	"time"
    28  )
    29  
    30  import (
    31  	uatomic "go.uber.org/atomic"
    32  )
    33  
    34  import (
    35  	gxchan "github.com/dubbogo/gost/container/chan"
    36  	gxlog "github.com/dubbogo/gost/log"
    37  )
    38  
    39  // nolint
    40  var ErrTimeChannelClosed = errors.New("timer channel closed")
    41  
    42  // InitDefaultTimerWheel initializes a default timer wheel
    43  func InitDefaultTimerWheel() {
    44  	defaultTimerWheelOnce.Do(func() {
    45  		defaultTimerWheel = NewTimerWheel()
    46  	})
    47  }
    48  
    49  func GetDefaultTimerWheel() *TimerWheel {
    50  	return defaultTimerWheel
    51  }
    52  
    53  // Now returns the current time.
    54  func Now() time.Time {
    55  	return defaultTimerWheel.Now()
    56  }
    57  
    58  ////////////////////////////////////////////////
    59  // timer node
    60  ////////////////////////////////////////////////
    61  
    62  var (
    63  	defaultTimerWheelOnce sync.Once
    64  	defaultTimerWheel     *TimerWheel
    65  	nextID                TimerID
    66  	curGxTime             = time.Now().UnixNano() // current goext time in nanoseconds
    67  )
    68  
    69  const (
    70  	maxMS     = 1000
    71  	maxSecond = 60
    72  	maxMinute = 60
    73  	maxHour   = 24
    74  	maxDay    = 31
    75  	// the time accuracy is millisecond.
    76  	minTickerInterval = 10e6
    77  	maxTimerLevel     = 5
    78  )
    79  
    80  func msNum(expire int64) int64     { return expire / int64(time.Millisecond) }
    81  func secondNum(expire int64) int64 { return expire / int64(time.Minute) }
    82  func minuteNum(expire int64) int64 { return expire / int64(time.Minute) }
    83  func hourNum(expire int64) int64   { return expire / int64(time.Hour) }
    84  func dayNum(expire int64) int64    { return expire / (maxHour * int64(time.Hour)) }
    85  
    86  // TimerFunc defines the time func.
    87  // if the return error is not nil, the related timer will be closed.
    88  type TimerFunc func(ID TimerID, expire time.Time, arg interface{}) error
    89  
    90  // TimerID is the id of a timer node
    91  type TimerID = uint64
    92  
    93  type timerNode struct {
    94  	ID       TimerID     // node id
    95  	trig     int64       // trigger time
    96  	typ      TimerType   // once or loop
    97  	period   int64       // loop period
    98  	timerRun TimerFunc   // timer func
    99  	arg      interface{} // func arg
   100  }
   101  
   102  func newTimerNode(f TimerFunc, typ TimerType, period int64, arg interface{}) *timerNode {
   103  	return &timerNode{
   104  		ID:       atomic.AddUint64(&nextID, 1),
   105  		trig:     atomic.LoadInt64(&curGxTime) + period,
   106  		typ:      typ,
   107  		period:   period,
   108  		timerRun: f,
   109  		arg:      arg,
   110  	}
   111  }
   112  
   113  func compareTimerNode(first, second *timerNode) int {
   114  	var ret int
   115  
   116  	if first.trig < second.trig {
   117  		ret = -1
   118  	} else if first.trig > second.trig {
   119  		ret = 1
   120  	} else {
   121  		ret = 0
   122  	}
   123  
   124  	return ret
   125  }
   126  
   127  type timerAction = int64
   128  
   129  const (
   130  	TimerActionAdd   timerAction = 1
   131  	TimerActionDel   timerAction = 2
   132  	TimerActionReset timerAction = 3
   133  )
   134  
   135  type timerNodeAction struct {
   136  	node   *timerNode
   137  	action timerAction
   138  }
   139  
   140  ////////////////////////////////////////////////
   141  // timer wheel
   142  ////////////////////////////////////////////////
   143  
   144  const (
   145  	timerNodeQueueSize = 128
   146  )
   147  
   148  var (
   149  	limit   = [maxTimerLevel + 1]int64{maxMS, maxSecond, maxMinute, maxHour, maxDay}
   150  	msLimit = [maxTimerLevel + 1]int64{
   151  		int64(time.Millisecond),
   152  		int64(time.Second),
   153  		int64(time.Minute),
   154  		int64(time.Hour),
   155  		int64(maxHour * time.Hour),
   156  	}
   157  )
   158  
   159  // TimerWheel is a timer based on multiple wheels
   160  type TimerWheel struct {
   161  	start  int64                     // start clock
   162  	clock  int64                     // current time in nanosecond
   163  	number uatomic.Int64             // timer node number
   164  	hand   [maxTimerLevel]int64      // clock
   165  	slot   [maxTimerLevel]*list.List // timer list
   166  
   167  	enable uatomic.Bool          // timer ready or closed
   168  	timerQ *gxchan.UnboundedChan // timer event notify channel
   169  
   170  	once   sync.Once      // for close ticker
   171  	ticker *time.Ticker   // virtual atomic clock
   172  	wg     sync.WaitGroup // gr sync
   173  }
   174  
   175  // NewTimerWheel returns a @TimerWheel object.
   176  func NewTimerWheel() *TimerWheel {
   177  	w := &TimerWheel{
   178  		clock: atomic.LoadInt64(&curGxTime),
   179  		// in fact, the minimum time accuracy is 10ms.
   180  		ticker: time.NewTicker(time.Duration(minTickerInterval)),
   181  		timerQ: gxchan.NewUnboundedChan(timerNodeQueueSize),
   182  	}
   183  
   184  	w.enable.Store(true)
   185  	w.start = w.clock
   186  
   187  	for i := 0; i < maxTimerLevel; i++ {
   188  		w.slot[i] = list.New()
   189  	}
   190  
   191  	w.wg.Add(1)
   192  	go func() {
   193  		defer w.wg.Done()
   194  		var (
   195  			t     time.Time
   196  			cFlag bool
   197  		)
   198  
   199  	LOOP:
   200  		for {
   201  			if !w.enable.Load() {
   202  				break LOOP
   203  			}
   204  
   205  			select {
   206  			case t, cFlag = <-w.ticker.C:
   207  				if !cFlag {
   208  					break LOOP
   209  				}
   210  
   211  				atomic.StoreInt64(&curGxTime, t.UnixNano())
   212  				ret := w.timerUpdate(t)
   213  				if ret == 0 {
   214  					w.run()
   215  				}
   216  			case node, qFlag := <-w.timerQ.Out():
   217  				if !qFlag {
   218  					break LOOP
   219  				}
   220  
   221  				nodeAction := node.(*timerNodeAction)
   222  				// just one w.timerQ channel to ensure the exec sequence of timer event.
   223  				switch {
   224  				case nodeAction.action == TimerActionAdd:
   225  					w.number.Add(1)
   226  					w.insertTimerNode(nodeAction.node)
   227  				case nodeAction.action == TimerActionDel:
   228  					w.number.Add(-1)
   229  					w.deleteTimerNode(nodeAction.node)
   230  				case nodeAction.action == TimerActionReset:
   231  					// log.CInfo("node action:%#v", nodeAction)
   232  					w.resetTimerNode(nodeAction.node)
   233  				default:
   234  					w.number.Add(1)
   235  					w.insertTimerNode(nodeAction.node)
   236  				}
   237  			}
   238  		}
   239  		log.Printf("the timeWheel runner exit, current timer node num:%d", w.number.Load())
   240  	}()
   241  	return w
   242  }
   243  
   244  func (w *TimerWheel) output() {
   245  	for idx := range w.slot {
   246  		log.Printf("print slot %d\n", idx)
   247  		// w.slot[idx].Output()
   248  	}
   249  }
   250  
   251  // TimerNumber returns the timer obj number in wheel
   252  func (w *TimerWheel) TimerNumber() int {
   253  	return int(w.number.Load())
   254  }
   255  
   256  // Now returns the current time
   257  func (w *TimerWheel) Now() time.Time {
   258  	return UnixNano2Time(atomic.LoadInt64(&curGxTime))
   259  }
   260  
   261  func (w *TimerWheel) run() {
   262  	var (
   263  		clock         int64
   264  		err           error
   265  		node          *timerNode
   266  		slot          *list.List
   267  		reinsertNodes []*timerNode
   268  	)
   269  
   270  	slot = w.slot[0]
   271  	clock = atomic.LoadInt64(&w.clock)
   272  	var next *list.Element
   273  	for e := slot.Front(); e != nil; e = next {
   274  		node = e.Value.(*timerNode)
   275  		if clock < node.trig {
   276  			break
   277  		}
   278  
   279  		err = node.timerRun(node.ID, UnixNano2Time(clock), node.arg)
   280  		if err == nil && node.typ == TimerLoop {
   281  			reinsertNodes = append(reinsertNodes, node)
   282  			// w.insertTimerNode(node)
   283  		} else {
   284  			w.number.Add(-1)
   285  		}
   286  
   287  		next = e.Next()
   288  		slot.Remove(e)
   289  	}
   290  
   291  	for _, reinsertNode := range reinsertNodes {
   292  		reinsertNode.trig += reinsertNode.period
   293  		w.insertTimerNode(reinsertNode)
   294  	}
   295  }
   296  
   297  func (w *TimerWheel) insertSlot(idx int, node *timerNode) {
   298  	var (
   299  		pos  *list.Element
   300  		slot *list.List
   301  	)
   302  
   303  	slot = w.slot[idx]
   304  	for e := slot.Front(); e != nil; e = e.Next() {
   305  		if compareTimerNode(node, e.Value.(*timerNode)) < 0 {
   306  			pos = e
   307  			break
   308  		}
   309  	}
   310  
   311  	if pos != nil {
   312  		slot.InsertBefore(node, pos)
   313  	} else {
   314  		// if slot is empty or @node_ptr is the maximum node
   315  		// in slot, insert it at the last of slot
   316  		slot.PushBack(node)
   317  	}
   318  }
   319  
   320  func (w *TimerWheel) deleteTimerNode(node *timerNode) {
   321  	var level int
   322  
   323  LOOP:
   324  	for level = range w.slot[:] {
   325  		for e := w.slot[level].Front(); e != nil; e = e.Next() {
   326  			if e.Value.(*timerNode).ID == node.ID {
   327  				w.slot[level].Remove(e)
   328  				// atomic.AddInt64(&w.number, -1)
   329  				break LOOP
   330  			}
   331  		}
   332  	}
   333  }
   334  
   335  func (w *TimerWheel) resetTimerNode(node *timerNode) {
   336  	var level int
   337  
   338  LOOP:
   339  	for level = range w.slot[:] {
   340  		for e := w.slot[level].Front(); e != nil; e = e.Next() {
   341  			if e.Value.(*timerNode).ID == node.ID {
   342  				n := e.Value.(*timerNode)
   343  				n.trig -= n.period
   344  				n.period = node.period
   345  				n.trig += n.period
   346  				w.slot[level].Remove(e)
   347  				w.insertTimerNode(n)
   348  				break LOOP
   349  			}
   350  		}
   351  	}
   352  }
   353  
   354  func (w *TimerWheel) deltaDiff(clock int64) int64 {
   355  	var handTime int64
   356  
   357  	for idx, hand := range w.hand[:] {
   358  		handTime += hand * msLimit[idx]
   359  	}
   360  
   361  	return clock - w.start - handTime
   362  }
   363  
   364  func (w *TimerWheel) insertTimerNode(node *timerNode) {
   365  	var (
   366  		idx  int
   367  		diff int64
   368  	)
   369  
   370  	diff = node.trig - atomic.LoadInt64(&w.clock)
   371  	switch {
   372  	case diff <= 0:
   373  		idx = 0
   374  	case dayNum(diff) != 0:
   375  		idx = 4
   376  	case hourNum(diff) != 0:
   377  		idx = 3
   378  	case minuteNum(diff) != 0:
   379  		idx = 2
   380  	case secondNum(diff) != 0:
   381  		idx = 1
   382  	default:
   383  		idx = 0
   384  	}
   385  
   386  	w.insertSlot(idx, node)
   387  }
   388  
   389  func (w *TimerWheel) timerCascade(level int) {
   390  	var (
   391  		guard bool
   392  		clock int64
   393  		diff  int64
   394  		cur   *timerNode
   395  	)
   396  
   397  	clock = atomic.LoadInt64(&w.clock)
   398  	var next *list.Element
   399  	for e := w.slot[level].Front(); e != nil; e = next {
   400  		cur = e.Value.(*timerNode)
   401  		diff = cur.trig - clock
   402  		switch {
   403  		case cur.trig <= clock:
   404  			guard = false
   405  		case level == 1:
   406  			guard = secondNum(diff) > 0
   407  		case level == 2:
   408  			guard = minuteNum(diff) > 0
   409  		case level == 3:
   410  			guard = hourNum(diff) > 0
   411  		case level == 4:
   412  			guard = dayNum(diff) > 0
   413  		}
   414  
   415  		if guard {
   416  			break
   417  		}
   418  
   419  		next = e.Next()
   420  		w.slot[level].Remove(e)
   421  
   422  		w.insertTimerNode(cur)
   423  	}
   424  }
   425  
   426  func (w *TimerWheel) timerUpdate(curTime time.Time) int {
   427  	var (
   428  		clock  int64
   429  		now    int64
   430  		idx    int32
   431  		diff   int64
   432  		maxIdx int32
   433  		inc    [maxTimerLevel + 1]int64
   434  	)
   435  
   436  	now = curTime.UnixNano()
   437  	clock = atomic.LoadInt64(&w.clock)
   438  	diff = now - clock
   439  	diff += w.deltaDiff(clock)
   440  	if diff < minTickerInterval*0.7 {
   441  		return -1
   442  	}
   443  	atomic.StoreInt64(&w.clock, now)
   444  
   445  	for idx = maxTimerLevel - 1; 0 <= idx; idx-- {
   446  		inc[idx] = diff / msLimit[idx]
   447  		diff %= msLimit[idx]
   448  	}
   449  
   450  	maxIdx = 0
   451  	for idx = 0; idx < maxTimerLevel; idx++ {
   452  		if 0 != inc[idx] {
   453  			w.hand[idx] += inc[idx]
   454  			inc[idx+1] += w.hand[idx] / limit[idx]
   455  			w.hand[idx] %= limit[idx]
   456  			maxIdx = idx + 1
   457  		}
   458  	}
   459  
   460  	for idx = 1; idx < maxIdx; idx++ {
   461  		w.timerCascade(int(idx))
   462  	}
   463  
   464  	return 0
   465  }
   466  
   467  // Stop stops the ticker
   468  func (w *TimerWheel) Stop() {
   469  	w.once.Do(func() {
   470  		w.enable.Store(false)
   471  		// close(w.timerQ) // to defend data race warning
   472  		w.ticker.Stop()
   473  	})
   474  }
   475  
   476  // Close stops the timer wheel and wait for all grs.
   477  func (w *TimerWheel) Close() {
   478  	w.Stop()
   479  	w.wg.Wait()
   480  }
   481  
   482  ////////////////////////////////////////////////
   483  // timer
   484  ////////////////////////////////////////////////
   485  
   486  // TimerType defines a timer task type.
   487  type TimerType int32
   488  
   489  const (
   490  	TimerOnce TimerType = 0x1 << 0
   491  	TimerLoop TimerType = 0x1 << 1
   492  )
   493  
   494  // AddTimer adds a timer asynchronously and returns a timer struct obj. It returns error if it failed.
   495  //
   496  // Attention that @f may block the timer gr. So u should create a gr to exec ur function asynchronously
   497  // if it may take a long time.
   498  //
   499  // args:
   500  //   @f: timer function.
   501  //   @typ: timer type
   502  //   @period: timer loop interval. its unit is nanosecond.
   503  //   @arg: timer argument which is used by @f.
   504  func (w *TimerWheel) AddTimer(f TimerFunc, typ TimerType, period time.Duration, arg interface{}) (*Timer, error) {
   505  	if !w.enable.Load() {
   506  		return nil, ErrTimeChannelClosed
   507  	}
   508  
   509  	t := &Timer{w: w}
   510  	node := newTimerNode(f, typ, int64(period), arg)
   511  	select {
   512  	case w.timerQ.In() <- &timerNodeAction{node: node, action: TimerActionAdd}:
   513  		t.ID = node.ID
   514  		return t, nil
   515  	}
   516  }
   517  
   518  func (w *TimerWheel) deleteTimer(t *Timer) error {
   519  	if !w.enable.Load() {
   520  		return ErrTimeChannelClosed
   521  	}
   522  
   523  	select {
   524  	case w.timerQ.In() <- &timerNodeAction{action: TimerActionDel, node: &timerNode{ID: t.ID}}:
   525  		return nil
   526  	}
   527  }
   528  
   529  func (w *TimerWheel) resetTimer(t *Timer, d time.Duration) error {
   530  	if !w.enable.Load() {
   531  		return ErrTimeChannelClosed
   532  	}
   533  
   534  	select {
   535  	case w.timerQ.In() <- &timerNodeAction{action: TimerActionReset, node: &timerNode{ID: t.ID, period: int64(d)}}:
   536  		return nil
   537  	}
   538  }
   539  
   540  func sendTime(_ TimerID, t time.Time, arg interface{}) error {
   541  	select {
   542  	case arg.(chan time.Time) <- t:
   543  	default:
   544  		// log.CInfo("sendTime default")
   545  	}
   546  
   547  	return nil
   548  }
   549  
   550  // NewTimer creates a new Timer that will send
   551  // the current time on its channel after at least duration d.
   552  func (w *TimerWheel) NewTimer(d time.Duration) *Timer {
   553  	c := make(chan time.Time, 1)
   554  	t := &Timer{
   555  		C: c,
   556  	}
   557  
   558  	timer, err := w.AddTimer(sendTime, TimerOnce, d, c)
   559  	if err == nil {
   560  		t.ID = timer.ID
   561  		t.w = timer.w
   562  		return t
   563  	}
   564  
   565  	gxlog.CError("addTimer fail, err is %v", err)
   566  	close(c)
   567  	return nil
   568  }
   569  
   570  // After waits for the duration to elapse and then sends the current time
   571  // on the returned channel.
   572  func (w *TimerWheel) After(d time.Duration) <-chan time.Time {
   573  	//timer := defaultTimer.NewTimer(d)
   574  	//if timer == nil {
   575  	//	return nil
   576  	//}
   577  	//
   578  	//return timer.C
   579  	return w.NewTimer(d).C
   580  }
   581  
   582  func goFunc(_ TimerID, _ time.Time, arg interface{}) error {
   583  	go arg.(func())()
   584  
   585  	return nil
   586  }
   587  
   588  // AfterFunc waits for the duration to elapse and then calls f
   589  // in its own goroutine. It returns a Timer that can
   590  // be used to cancel the call using its Stop method.
   591  func (w *TimerWheel) AfterFunc(d time.Duration, f func()) *Timer {
   592  	t, _ := w.AddTimer(goFunc, TimerOnce, d, f)
   593  
   594  	return t
   595  }
   596  
   597  // Sleep pauses the current goroutine for at least the duration d.
   598  // A negative or zero duration causes Sleep to return immediately.
   599  func (w *TimerWheel) Sleep(d time.Duration) {
   600  	<-w.NewTimer(d).C
   601  }
   602  
   603  ////////////////////////////////////////////////
   604  // ticker
   605  ////////////////////////////////////////////////
   606  
   607  // NewTicker returns a new Ticker containing a channel that will send
   608  // the time on the channel after each tick. The period of the ticks is
   609  // specified by the duration argument. The ticker will adjust the time
   610  // interval or drop ticks to make up for slow receivers.
   611  // The duration d must be greater than zero; if not, NewTicker will
   612  // panic. Stop the ticker to release associated resources.
   613  func (w *TimerWheel) NewTicker(d time.Duration) *Ticker {
   614  	c := make(chan time.Time, 1)
   615  
   616  	timer, err := w.AddTimer(sendTime, TimerLoop, d, c)
   617  	if err == nil {
   618  		timer.C = c
   619  		return (*Ticker)(timer)
   620  	}
   621  
   622  	gxlog.CError("addTimer fail, err is %v", err)
   623  	close(c)
   624  	return nil
   625  }
   626  
   627  // TickFunc returns a Ticker
   628  func (w *TimerWheel) TickFunc(d time.Duration, f func()) *Ticker {
   629  	t, err := w.AddTimer(goFunc, TimerLoop, d, f)
   630  	if err == nil {
   631  		return (*Ticker)(t)
   632  	}
   633  	gxlog.CError("addTimer fail, err is %v", err)
   634  
   635  	return nil
   636  }
   637  
   638  // Tick is a convenience wrapper for NewTicker providing access to the ticking
   639  // channel only. While Tick is useful for clients that have no need to shut down
   640  // the Ticker, be aware that without a way to shut it down the underlying
   641  // Ticker cannot be recovered by the garbage collector; it "leaks".
   642  // Unlike NewTicker, Tick will return nil if d <= 0.
   643  func (w *TimerWheel) Tick(d time.Duration) <-chan time.Time {
   644  	return w.NewTicker(d).C
   645  }