github.com/polarismesh/polaris@v1.17.8/common/timewheel/timewheel.go (about)

     1  /**
     2   * Tencent is pleased to support the open source community by making Polaris available.
     3   *
     4   * Copyright (C) 2019 THL A29 Limited, a Tencent company. All rights reserved.
     5   *
     6   * Licensed under the BSD 3-Clause License (the "License");
     7   * you may not use this file except in compliance with the License.
     8   * You may obtain a copy of the License at
     9   *
    10   * https://opensource.org/licenses/BSD-3-Clause
    11   *
    12   * Unless required by applicable law or agreed to in writing, software distributed
    13   * under the License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR
    14   * CONDITIONS OF ANY KIND, either express or implied. See the License for the
    15   * specific language governing permissions and limitations under the License.
    16   */
    17  
    18  package timewheel
    19  
    20  import (
    21  	"container/list"
    22  	"sync"
    23  	"time"
    24  )
    25  
    26  // a simple routine-safe timewheel, can only add task
    27  // not support update/delete
    28  
    29  // TimeWheel 时间轮结构体
    30  type TimeWheel struct {
    31  	name       string
    32  	interval   time.Duration
    33  	ticker     *time.Ticker
    34  	currentPos int
    35  	slots      []*list.List
    36  	locks      []sync.Mutex
    37  	slotNum    int
    38  	stopCh     chan struct{}
    39  	wg         sync.WaitGroup
    40  	opts       options
    41  }
    42  
    43  type options struct {
    44  	waitTaskOnClose bool
    45  }
    46  
    47  type Option func(*options)
    48  
    49  func WithWaitTaskOnClose(waitTaskOnClose bool) Option {
    50  	return func(o *options) {
    51  		o.waitTaskOnClose = waitTaskOnClose
    52  	}
    53  }
    54  
    55  // Callback 时间轮回调函数定义
    56  type Callback func(interface{})
    57  
    58  // Task 时间轮任务结构体
    59  type Task struct {
    60  	delayTime time.Duration
    61  	circle    int
    62  	callback  Callback
    63  	taskData  interface{}
    64  }
    65  
    66  // New 初始化时间轮
    67  func New(interval time.Duration, slotNum int, name string, opts ...Option) *TimeWheel {
    68  	if interval <= 0 || slotNum <= 0 {
    69  		return nil
    70  	}
    71  	op := options{
    72  		waitTaskOnClose: true,
    73  	}
    74  
    75  	for _, option := range opts {
    76  		option(&op)
    77  	}
    78  
    79  	timeWheel := &TimeWheel{
    80  		name:       name,
    81  		interval:   interval,
    82  		slots:      make([]*list.List, slotNum),
    83  		locks:      make([]sync.Mutex, slotNum),
    84  		currentPos: -1,
    85  		slotNum:    slotNum,
    86  		stopCh:     make(chan struct{}, 1),
    87  		opts:       op,
    88  	}
    89  
    90  	for i := 0; i < slotNum; i++ {
    91  		timeWheel.slots[i] = list.New()
    92  	}
    93  
    94  	return timeWheel
    95  }
    96  
    97  // Start 启动时间轮
    98  func (tw *TimeWheel) Start() {
    99  	tw.ticker = time.NewTicker(tw.interval)
   100  	go tw.start()
   101  }
   102  
   103  // Stop 停止时间轮
   104  func (tw *TimeWheel) Stop() {
   105  	close(tw.stopCh)
   106  	if tw.opts.waitTaskOnClose {
   107  		tw.wg.Wait()
   108  	}
   109  }
   110  
   111  // start 时间轮运转函数
   112  func (tw *TimeWheel) start() {
   113  	for {
   114  		select {
   115  		case <-tw.ticker.C:
   116  			tw.taskRunner()
   117  		case <-tw.stopCh:
   118  			tw.ticker.Stop()
   119  			return
   120  		}
   121  	}
   122  }
   123  
   124  // taskRunner 时间轮到期处理函数
   125  func (tw *TimeWheel) taskRunner() {
   126  	tw.currentPos++
   127  	if tw.currentPos == tw.slotNum {
   128  		tw.currentPos = 0
   129  	}
   130  
   131  	l := tw.slots[tw.currentPos]
   132  	tw.locks[tw.currentPos].Lock()
   133  	// execNum := tw.scanAddRunTask(l)
   134  	_ = tw.scanAddRunTask(l)
   135  	tw.locks[tw.currentPos].Unlock()
   136  
   137  	// log.Debugf("%s task start time:%d, use time:%v, exec num:%d", tw.name, now.Unix(), time.Since(now), execNum)
   138  }
   139  
   140  // AddTask 新增时间轮任务
   141  func (tw *TimeWheel) AddTask(delayMilli uint32, data interface{}, cb Callback) {
   142  	delayTime := time.Duration(delayMilli) * time.Millisecond
   143  	task := &Task{delayTime: delayTime, taskData: data, callback: cb}
   144  	pos, circle := tw.getSlots(task.delayTime)
   145  	task.circle = circle
   146  
   147  	tw.locks[pos].Lock()
   148  	tw.slots[pos].PushBack(task)
   149  	tw.locks[pos].Unlock()
   150  }
   151  
   152  // scanAddRunTask 运行时间轮任务
   153  func (tw *TimeWheel) scanAddRunTask(l *list.List) int {
   154  	if l == nil || l.Len() == 0 {
   155  		return 0
   156  	}
   157  
   158  	execNum := l.Len()
   159  	for item := l.Front(); item != nil; {
   160  		task := item.Value.(*Task)
   161  
   162  		if task.circle > 0 {
   163  			task.circle--
   164  			item = item.Next()
   165  			continue
   166  		}
   167  
   168  		go func() {
   169  			if tw.opts.waitTaskOnClose {
   170  				tw.wg.Add(1)
   171  				defer tw.wg.Done()
   172  			}
   173  			task.callback(task.taskData)
   174  		}()
   175  		next := item.Next()
   176  		l.Remove(item)
   177  		item = next
   178  	}
   179  
   180  	return execNum
   181  }
   182  
   183  // getSlots 获取当前时间轮位置
   184  func (tw *TimeWheel) getSlots(d time.Duration) (pos int, circle int) {
   185  	delayTime := int(d.Seconds())
   186  	interval := int(tw.interval.Seconds())
   187  	circle = delayTime / interval / tw.slotNum
   188  	pos = tw.currentPos
   189  	if pos == -1 {
   190  		pos = 0
   191  	}
   192  	pos = (pos + delayTime/interval) % tw.slotNum
   193  	if pos == tw.currentPos {
   194  		circle--
   195  	}
   196  	return
   197  }