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 }