``` feat(pet): 重构宠物繁殖系统,添加蛋孵化功能
This commit is contained in:
21
common/utils/timer/LICENSE
Normal file
21
common/utils/timer/LICENSE
Normal file
@@ -0,0 +1,21 @@
|
||||
MIT License
|
||||
|
||||
Copyright (c) 2020-2021 蚂蚁实验室
|
||||
|
||||
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||
of this software and associated documentation files (the "Software"), to deal
|
||||
in the Software without restriction, including without limitation the rights
|
||||
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||
copies of the Software, and to permit persons to whom the Software is
|
||||
furnished to do so, subject to the following conditions:
|
||||
|
||||
The above copyright notice and this permission notice shall be included in all
|
||||
copies or substantial portions of the Software.
|
||||
|
||||
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||
SOFTWARE.
|
||||
130
common/utils/timer/README.md
Normal file
130
common/utils/timer/README.md
Normal file
@@ -0,0 +1,130 @@
|
||||
## timer
|
||||
[](https://github.com/antlabs/timer/actions)
|
||||
[](https://codecov.io/gh/antlabs/timer)
|
||||
|
||||
timer是高性能定时器库
|
||||
## feature
|
||||
* 支持一次性定时器
|
||||
* 支持周期性定时器
|
||||
* 支持多种数据结构后端,最小堆,5级时间轮
|
||||
|
||||
## 一次性定时器
|
||||
```go
|
||||
import (
|
||||
"github.com/antlabs/timer"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tm := timer.NewTimer()
|
||||
|
||||
tm.AfterFunc(1*time.Second, func() {
|
||||
log.Printf("after\n")
|
||||
})
|
||||
|
||||
tm.AfterFunc(10*time.Second, func() {
|
||||
log.Printf("after\n")
|
||||
})
|
||||
tm.Run()
|
||||
}
|
||||
```
|
||||
## 周期性定时器
|
||||
```go
|
||||
import (
|
||||
"github.com/antlabs/timer"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tm := timer.NewTimer()
|
||||
|
||||
tm.ScheduleFunc(1*time.Second, func() {
|
||||
log.Printf("schedule\n")
|
||||
})
|
||||
|
||||
tm.Run()
|
||||
}
|
||||
```
|
||||
## 自定义周期性定时器
|
||||
实现时间翻倍定时的例子
|
||||
```go
|
||||
type curstomTest struct {
|
||||
count int
|
||||
}
|
||||
// 只要实现Next接口就行
|
||||
func (c *curstomTest) Next(now time.Time) (rv time.Time) {
|
||||
rv = now.Add(time.Duration(c.count) * time.Millisecond * 10)
|
||||
c.count++
|
||||
return
|
||||
}
|
||||
|
||||
func main() {
|
||||
tm := timer.NewTimer(timer.WithMinHeap())
|
||||
node := tm.CustomFunc(&curstomTest{count: 1}, func() {
|
||||
log.Printf("%v\n", time.Now())
|
||||
})
|
||||
tm.Run()
|
||||
}
|
||||
```
|
||||
## 取消某一个定时器
|
||||
```go
|
||||
import (
|
||||
"log"
|
||||
"time"
|
||||
|
||||
"github.com/antlabs/timer"
|
||||
)
|
||||
|
||||
func main() {
|
||||
|
||||
tm := timer.NewTimer()
|
||||
|
||||
// 只会打印2 time.Second
|
||||
tm.AfterFunc(2*time.Second, func() {
|
||||
log.Printf("2 time.Second")
|
||||
})
|
||||
|
||||
// tk3 会被 tk3.Stop()函数调用取消掉
|
||||
tk3 := tm.AfterFunc(3*time.Second, func() {
|
||||
log.Printf("3 time.Second")
|
||||
})
|
||||
|
||||
tk3.Stop() //取消tk3
|
||||
|
||||
tm.Run()
|
||||
}
|
||||
```
|
||||
## 选择不同的的数据结构
|
||||
```go
|
||||
import (
|
||||
"github.com/antlabs/timer"
|
||||
"log"
|
||||
)
|
||||
|
||||
func main() {
|
||||
tm := timer.NewTimer(timer.WithMinHeap())// 选择最小堆,默认时间轮
|
||||
}
|
||||
```
|
||||
## benchmark
|
||||
|
||||
github.com/antlabs/timer 性能最高
|
||||
```
|
||||
goos: linux
|
||||
goarch: amd64
|
||||
pkg: benchmark
|
||||
Benchmark_antlabs_Timer_AddTimer/N-1m-16 9177537 124 ns/op
|
||||
Benchmark_antlabs_Timer_AddTimer/N-5m-16 10152950 128 ns/op
|
||||
Benchmark_antlabs_Timer_AddTimer/N-10m-16 9955639 127 ns/op
|
||||
Benchmark_RussellLuo_Timingwheel_AddTimer/N-1m-16 5316916 222 ns/op
|
||||
Benchmark_RussellLuo_Timingwheel_AddTimer/N-5m-16 5848843 218 ns/op
|
||||
Benchmark_RussellLuo_Timingwheel_AddTimer/N-10m-16 5872621 231 ns/op
|
||||
Benchmark_ouqiang_Timewheel/N-1m-16 720667 1622 ns/op
|
||||
Benchmark_ouqiang_Timewheel/N-5m-16 807018 1573 ns/op
|
||||
Benchmark_ouqiang_Timewheel/N-10m-16 666183 1557 ns/op
|
||||
Benchmark_Stdlib_AddTimer/N-1m-16 8031864 144 ns/op
|
||||
Benchmark_Stdlib_AddTimer/N-5m-16 8437442 151 ns/op
|
||||
Benchmark_Stdlib_AddTimer/N-10m-16 8080659 167 ns/op
|
||||
|
||||
```
|
||||
* 压测代码位于
|
||||
https://github.com/junelabs/timer-benchmark
|
||||
1
common/utils/timer/_long-time-test/build.sh
Executable file
1
common/utils/timer/_long-time-test/build.sh
Executable file
@@ -0,0 +1 @@
|
||||
go build -race long-time-test.go
|
||||
157
common/utils/timer/_long-time-test/long-time-test.go
Normal file
157
common/utils/timer/_long-time-test/long-time-test.go
Normal file
@@ -0,0 +1,157 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/antlabs/timer"
|
||||
)
|
||||
|
||||
// 这是一个长时间测试代码
|
||||
|
||||
// 测试周期执行
|
||||
func schedule(tm timer.Timer) {
|
||||
tm.ScheduleFunc(200*time.Millisecond, func() {
|
||||
log.Printf("schedule 200 milliseconds\n")
|
||||
})
|
||||
|
||||
tm.ScheduleFunc(time.Second, func() {
|
||||
log.Printf("schedule second\n")
|
||||
})
|
||||
|
||||
tm.ScheduleFunc(1*time.Minute, func() {
|
||||
log.Printf("schedule minute\n")
|
||||
})
|
||||
|
||||
tm.ScheduleFunc(1*time.Hour, func() {
|
||||
log.Printf("schedule hour\n")
|
||||
})
|
||||
|
||||
tm.ScheduleFunc(24*time.Hour, func() {
|
||||
log.Printf("schedule day\n")
|
||||
})
|
||||
}
|
||||
|
||||
// 测试一次性定时器
|
||||
func after(tm timer.Timer) {
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(4)
|
||||
defer wg.Wait()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 3600*24; i++ {
|
||||
i := i
|
||||
tm.AfterFunc(time.Second, func() {
|
||||
log.Printf("after second:%d\n", i)
|
||||
})
|
||||
time.Sleep(900 * time.Millisecond)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 60*24; i++ {
|
||||
i := i
|
||||
tm.AfterFunc(time.Minute, func() {
|
||||
log.Printf("after minute:%d\n", i)
|
||||
})
|
||||
time.Sleep(50 * time.Second)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 24; i++ {
|
||||
i := i
|
||||
tm.AfterFunc(time.Hour, func() {
|
||||
log.Printf("after hour:%d\n", i)
|
||||
})
|
||||
time.Sleep(59 * time.Minute)
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 1; i++ {
|
||||
i := i
|
||||
tm.AfterFunc(24*time.Hour, func() {
|
||||
log.Printf("after day:%d\n", i)
|
||||
})
|
||||
time.Sleep(59 * time.Minute)
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// 检测 stop after 消息,没有打印是正确的行为
|
||||
func stopNode(tm timer.Timer) {
|
||||
|
||||
var wg sync.WaitGroup
|
||||
wg.Add(4)
|
||||
defer wg.Wait()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 3600*24; i++ {
|
||||
i := i
|
||||
node := tm.AfterFunc(time.Second, func() {
|
||||
log.Printf("stop after second:%d\n", i)
|
||||
})
|
||||
time.Sleep(900 * time.Millisecond)
|
||||
node.Stop()
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 60*24; i++ {
|
||||
i := i
|
||||
node := tm.AfterFunc(time.Minute, func() {
|
||||
log.Printf("stop after minute:%d\n", i)
|
||||
})
|
||||
time.Sleep(50 * time.Second)
|
||||
node.Stop()
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 24; i++ {
|
||||
i := i
|
||||
node := tm.AfterFunc(time.Hour, func() {
|
||||
log.Printf("stop after hour:%d\n", i)
|
||||
})
|
||||
time.Sleep(59 * time.Minute)
|
||||
node.Stop()
|
||||
}
|
||||
}()
|
||||
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
for i := 0; i < 1; i++ {
|
||||
i := i
|
||||
node := tm.AfterFunc(23*time.Hour, func() {
|
||||
log.Printf("stop after day:%d\n", i)
|
||||
})
|
||||
time.Sleep(22 * time.Hour)
|
||||
node.Stop()
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
func main() {
|
||||
|
||||
log.SetFlags(log.Ldate | log.Lmicroseconds)
|
||||
tm := timer.NewTimer()
|
||||
|
||||
go schedule(tm)
|
||||
go after(tm)
|
||||
go stopNode(tm)
|
||||
|
||||
go func() {
|
||||
time.Sleep(time.Hour*24 + time.Hour)
|
||||
tm.Stop()
|
||||
}()
|
||||
tm.Run()
|
||||
}
|
||||
5
common/utils/timer/go.mod
Normal file
5
common/utils/timer/go.mod
Normal file
@@ -0,0 +1,5 @@
|
||||
module github.com/antlabs/timer
|
||||
|
||||
go 1.19
|
||||
|
||||
require github.com/antlabs/stl v0.0.2
|
||||
2
common/utils/timer/go.sum
Normal file
2
common/utils/timer/go.sum
Normal file
@@ -0,0 +1,2 @@
|
||||
github.com/antlabs/stl v0.0.2 h1:sna1AXR5yIkNE9lWhCcKbheFJSVfCa3vugnGyakI79s=
|
||||
github.com/antlabs/stl v0.0.2/go.mod h1:kKrO4xrn9cfS1mJVo+/BqePZjAYMXqD0amGF2Ouq7ac=
|
||||
221
common/utils/timer/min_heap.go
Normal file
221
common/utils/timer/min_heap.go
Normal file
@@ -0,0 +1,221 @@
|
||||
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
|
||||
//
|
||||
// mit license
|
||||
package timer
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"context"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
|
||||
"github.com/panjf2000/ants/v2"
|
||||
)
|
||||
|
||||
var _ Timer = (*minHeap)(nil)
|
||||
|
||||
var defaultTimeout = time.Hour
|
||||
|
||||
type minHeap struct {
|
||||
mu sync.Mutex
|
||||
minHeaps
|
||||
chAdd chan struct{}
|
||||
ctx context.Context
|
||||
cancel context.CancelFunc
|
||||
wait sync.WaitGroup
|
||||
tm *time.Timer
|
||||
runCount int32 // 单元测试时使用
|
||||
}
|
||||
|
||||
// 一次性定时器
|
||||
func (m *minHeap) AfterFunc(expire time.Duration, callback func()) TimeNoder {
|
||||
return m.addCallback(expire, nil, callback, false)
|
||||
}
|
||||
|
||||
// 周期性定时器
|
||||
func (m *minHeap) ScheduleFunc(expire time.Duration, callback func()) TimeNoder {
|
||||
return m.addCallback(expire, nil, callback, true)
|
||||
}
|
||||
|
||||
// 自定义下次的时间
|
||||
func (m *minHeap) CustomFunc(n Next, callback func()) TimeNoder {
|
||||
return m.addCallback(time.Duration(0), n, callback, true)
|
||||
}
|
||||
|
||||
// 加任务
|
||||
func (m *minHeap) addCallback(expire time.Duration, n Next, callback func(), isSchedule bool) TimeNoder {
|
||||
select {
|
||||
case <-m.ctx.Done():
|
||||
panic("cannot add a task to a closed timer")
|
||||
default:
|
||||
}
|
||||
|
||||
node := minHeapNode{
|
||||
callback: callback,
|
||||
userExpire: expire,
|
||||
next: n,
|
||||
absExpire: time.Now().Add(expire),
|
||||
isSchedule: isSchedule,
|
||||
root: m,
|
||||
}
|
||||
|
||||
if n != nil {
|
||||
node.absExpire = n.Next(time.Now())
|
||||
}
|
||||
|
||||
m.mu.Lock()
|
||||
heap.Push(&m.minHeaps, &node)
|
||||
m.wait.Add(1)
|
||||
m.mu.Unlock()
|
||||
|
||||
select {
|
||||
case m.chAdd <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
|
||||
return &node
|
||||
}
|
||||
|
||||
func (m *minHeap) removeTimeNode(node *minHeapNode) {
|
||||
m.mu.Lock()
|
||||
if node.index < 0 || node.index > int32(len(m.minHeaps)) || int32(len(m.minHeaps)) == 0 {
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
heap.Remove(&m.minHeaps, int(node.index))
|
||||
m.wait.Done()
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *minHeap) resetTimeNode(node *minHeapNode, d time.Duration) {
|
||||
m.mu.Lock()
|
||||
node.userExpire = d
|
||||
node.absExpire = time.Now().Add(d)
|
||||
heap.Fix(&m.minHeaps, int(node.index))
|
||||
select {
|
||||
case m.chAdd <- struct{}{}:
|
||||
default:
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
|
||||
func (m *minHeap) getNewSleepTime() time.Duration {
|
||||
if m.minHeaps.Len() == 0 {
|
||||
return time.Hour
|
||||
}
|
||||
|
||||
timeout := time.Until(m.minHeaps[0].absExpire)
|
||||
if timeout < 0 {
|
||||
timeout = 0
|
||||
}
|
||||
return timeout
|
||||
}
|
||||
|
||||
var pool, _ = ants.NewPool(-1)
|
||||
|
||||
func (m *minHeap) process() {
|
||||
for {
|
||||
m.mu.Lock()
|
||||
now := time.Now()
|
||||
// 如果堆中没有元素,就等待
|
||||
// 这时候设置一个相对长的时间,避免空转cpu
|
||||
if m.minHeaps.Len() == 0 {
|
||||
m.tm.Reset(time.Hour)
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
for {
|
||||
// 取出最小堆的第一个元素
|
||||
first := m.minHeaps[0]
|
||||
|
||||
// 时间未到直接过滤掉
|
||||
// 只是跳过最近的循环
|
||||
if !now.After(first.absExpire) {
|
||||
break
|
||||
}
|
||||
|
||||
// 取出待执行的callback
|
||||
callback := first.callback
|
||||
// 如果是周期性任务
|
||||
if first.isSchedule {
|
||||
// 计算下次触发的绝对时间点
|
||||
first.absExpire = first.Next(now)
|
||||
// 修改下在堆中的位置
|
||||
heap.Fix(&m.minHeaps, int(first.index))
|
||||
} else {
|
||||
// 从堆中删除
|
||||
heap.Pop(&m.minHeaps)
|
||||
m.wait.Done()
|
||||
}
|
||||
|
||||
// 正在运行的任务数加1
|
||||
atomic.AddInt32(&m.runCount, 1)
|
||||
pool.Submit(func() {
|
||||
callback()
|
||||
// 对正在运行的任务数减1
|
||||
atomic.AddInt32(&m.runCount, -1)
|
||||
})
|
||||
|
||||
// 如果堆中没有元素,就等待
|
||||
if m.minHeaps.Len() == 0 {
|
||||
m.tm.Reset(defaultTimeout)
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
}
|
||||
|
||||
// 取出第一个元素
|
||||
first := m.minHeaps[0]
|
||||
// 如果第一个元素的时间还没到,就计算下次触发的时间
|
||||
if time.Now().Before(first.absExpire) {
|
||||
to := m.getNewSleepTime()
|
||||
m.tm.Reset(to)
|
||||
// fmt.Printf("### now=%v, to = %v, m.minHeaps[0].absExpire = %v\n", time.Now(), to, m.minHeaps[0].absExpire)
|
||||
m.mu.Unlock()
|
||||
return
|
||||
}
|
||||
m.mu.Unlock()
|
||||
}
|
||||
}
|
||||
|
||||
// 运行
|
||||
// 为了避免空转cpu, 会等待一个chan, 只要AfterFunc或者ScheduleFunc被调用就会往这个chan里面写值
|
||||
func (m *minHeap) Run() {
|
||||
m.tm = time.NewTimer(time.Hour)
|
||||
m.process()
|
||||
for {
|
||||
select {
|
||||
case <-m.tm.C:
|
||||
m.process()
|
||||
case <-m.chAdd:
|
||||
m.mu.Lock()
|
||||
// 极端情况,加完任务立即给删除了, 判断下当前堆中是否有元素
|
||||
if m.minHeaps.Len() > 0 {
|
||||
m.tm.Reset(m.getNewSleepTime())
|
||||
}
|
||||
m.mu.Unlock()
|
||||
// 进入事件循环,如果为空就会从事件循环里面退出
|
||||
case <-m.ctx.Done():
|
||||
// 等待所有任务结束
|
||||
m.wait.Wait()
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
// 停止所有定时器
|
||||
func (m *minHeap) Stop() {
|
||||
m.cancel()
|
||||
}
|
||||
|
||||
func newMinHeap() (mh *minHeap) {
|
||||
mh = &minHeap{}
|
||||
heap.Init(&mh.minHeaps)
|
||||
mh.chAdd = make(chan struct{}, 1024)
|
||||
mh.ctx, mh.cancel = context.WithCancel(context.TODO())
|
||||
return
|
||||
}
|
||||
62
common/utils/timer/min_heap_node.go
Normal file
62
common/utils/timer/min_heap_node.go
Normal file
@@ -0,0 +1,62 @@
|
||||
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
|
||||
//
|
||||
// mit license
|
||||
package timer
|
||||
|
||||
import (
|
||||
"time"
|
||||
)
|
||||
|
||||
type minHeapNode struct {
|
||||
callback func() // 用户的callback
|
||||
absExpire time.Time // 绝对时间
|
||||
userExpire time.Duration // 过期时间段
|
||||
root *minHeap // 指向最小堆
|
||||
next Next // 自定义下个触发的时间点, cronex项目用到了
|
||||
index int32 // 在min heap中的索引,方便删除或者重新推入堆中
|
||||
isSchedule bool // 是否是周期性任务
|
||||
}
|
||||
|
||||
func (m *minHeapNode) Stop() bool {
|
||||
m.root.removeTimeNode(m)
|
||||
return true
|
||||
}
|
||||
func (m *minHeapNode) Reset(d time.Duration) bool {
|
||||
m.root.resetTimeNode(m, d)
|
||||
return true
|
||||
}
|
||||
|
||||
func (m *minHeapNode) Next(now time.Time) time.Time {
|
||||
if m.next != nil {
|
||||
return (m.next).Next(now)
|
||||
}
|
||||
return now.Add(m.userExpire)
|
||||
}
|
||||
|
||||
type minHeaps []*minHeapNode
|
||||
|
||||
func (m minHeaps) Len() int { return len(m) }
|
||||
|
||||
func (m minHeaps) Less(i, j int) bool { return m[i].absExpire.Before(m[j].absExpire) }
|
||||
|
||||
func (m minHeaps) Swap(i, j int) {
|
||||
m[i], m[j] = m[j], m[i]
|
||||
m[i].index = int32(i)
|
||||
m[j].index = int32(j)
|
||||
}
|
||||
|
||||
func (m *minHeaps) Push(x any) {
|
||||
// Push and Pop use pointer receivers because they modify the slice's length,
|
||||
// not just its contents.
|
||||
*m = append(*m, x.(*minHeapNode))
|
||||
lastIndex := int32(len(*m) - 1)
|
||||
(*m)[lastIndex].index = lastIndex
|
||||
}
|
||||
|
||||
func (m *minHeaps) Pop() any {
|
||||
old := *m
|
||||
n := len(old)
|
||||
x := old[n-1]
|
||||
*m = old[0 : n-1]
|
||||
return x
|
||||
}
|
||||
64
common/utils/timer/min_heap_node_test.go
Normal file
64
common/utils/timer/min_heap_node_test.go
Normal file
@@ -0,0 +1,64 @@
|
||||
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
|
||||
//
|
||||
// mit license
|
||||
|
||||
package timer
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_NodeSizeof(t *testing.T) {
|
||||
t.Run("输出最小堆node的sizeof", func(t *testing.T) {
|
||||
// t.Logf("minHeapNode size: %d, %d\n", unsafe.Sizeof(minHeapNode{}), unsafe.Sizeof(time.Timer{}))
|
||||
})
|
||||
}
|
||||
func Test_MinHeap(t *testing.T) {
|
||||
t.Run("", func(t *testing.T) {
|
||||
var mh minHeaps
|
||||
now := time.Now()
|
||||
n1 := minHeapNode{
|
||||
absExpire: now.Add(time.Second),
|
||||
userExpire: 1 * time.Second,
|
||||
}
|
||||
|
||||
n2 := minHeapNode{
|
||||
absExpire: now.Add(2 * time.Second),
|
||||
userExpire: 2 * time.Second,
|
||||
}
|
||||
|
||||
n3 := minHeapNode{
|
||||
absExpire: now.Add(3 * time.Second),
|
||||
userExpire: 3 * time.Second,
|
||||
}
|
||||
|
||||
n6 := minHeapNode{
|
||||
absExpire: now.Add(6 * time.Second),
|
||||
userExpire: 6 * time.Second,
|
||||
}
|
||||
n5 := minHeapNode{
|
||||
absExpire: now.Add(5 * time.Second),
|
||||
userExpire: 5 * time.Second,
|
||||
}
|
||||
n4 := minHeapNode{
|
||||
absExpire: now.Add(4 * time.Second),
|
||||
userExpire: 4 * time.Second,
|
||||
}
|
||||
mh.Push(&n1)
|
||||
mh.Push(&n2)
|
||||
mh.Push(&n3)
|
||||
mh.Push(&n6)
|
||||
mh.Push(&n5)
|
||||
mh.Push(&n4)
|
||||
|
||||
for i := 1; len(mh) > 0; i++ {
|
||||
v := heap.Pop(&mh).(*minHeapNode)
|
||||
|
||||
if v.userExpire != time.Duration(i)*time.Second {
|
||||
t.Errorf("index(%d) v.userExpire(%v) != %v", i, v.userExpire, time.Duration(i)*time.Second)
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
329
common/utils/timer/min_heap_test.go
Normal file
329
common/utils/timer/min_heap_test.go
Normal file
@@ -0,0 +1,329 @@
|
||||
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
|
||||
//
|
||||
// mit license
|
||||
package timer
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
// 测试AfterFunc有没有运行以及时间间隔可对
|
||||
func Test_MinHeap_AfterFunc_Run(t *testing.T) {
|
||||
t.Run("1ms", func(t *testing.T) {
|
||||
tm := NewTimer(WithMinHeap())
|
||||
|
||||
go tm.Run()
|
||||
count := int32(0)
|
||||
|
||||
tc := make(chan time.Duration, 2)
|
||||
|
||||
var mu sync.Mutex
|
||||
isClose := false
|
||||
now := time.Now()
|
||||
node1 := tm.AfterFunc(time.Millisecond, func() {
|
||||
|
||||
mu.Lock()
|
||||
atomic.AddInt32(&count, 1)
|
||||
if atomic.LoadInt32(&count) <= 2 && !isClose {
|
||||
tc <- time.Since(now)
|
||||
}
|
||||
mu.Unlock()
|
||||
})
|
||||
|
||||
node2 := tm.AfterFunc(time.Millisecond, func() {
|
||||
mu.Lock()
|
||||
atomic.AddInt32(&count, 1)
|
||||
if atomic.LoadInt32(&count) <= 2 && !isClose {
|
||||
tc <- time.Since(now)
|
||||
}
|
||||
mu.Unlock()
|
||||
})
|
||||
|
||||
time.Sleep(time.Millisecond * 3)
|
||||
mu.Lock()
|
||||
isClose = true
|
||||
close(tc)
|
||||
node1.Stop()
|
||||
node2.Stop()
|
||||
mu.Unlock()
|
||||
for tv := range tc {
|
||||
if tv < time.Millisecond || tv > 2*time.Millisecond {
|
||||
t.Errorf("tc < time.Millisecond tc > 2*time.Millisecond")
|
||||
|
||||
}
|
||||
}
|
||||
if atomic.LoadInt32(&count) != 2 {
|
||||
t.Errorf("count:%d != 2", atomic.LoadInt32(&count))
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
t.Run("10ms", func(t *testing.T) {
|
||||
tm := NewTimer(WithMinHeap())
|
||||
|
||||
go tm.Run() // 运行事件循环
|
||||
count := int32(0)
|
||||
tc := make(chan time.Duration, 2)
|
||||
|
||||
var mu sync.Mutex
|
||||
isClosed := false
|
||||
now := time.Now()
|
||||
node1 := tm.AfterFunc(time.Millisecond*10, func() {
|
||||
now2 := time.Now()
|
||||
mu.Lock()
|
||||
atomic.AddInt32(&count, 1)
|
||||
if atomic.LoadInt32(&count) <= 2 && !isClosed {
|
||||
tc <- time.Since(now)
|
||||
}
|
||||
mu.Unlock()
|
||||
log.Printf("node1.Lock:%v\n", time.Since(now2))
|
||||
})
|
||||
node2 := tm.AfterFunc(time.Millisecond*10, func() {
|
||||
now2 := time.Now()
|
||||
mu.Lock()
|
||||
atomic.AddInt32(&count, 1)
|
||||
if atomic.LoadInt32(&count) <= 2 && !isClosed {
|
||||
tc <- time.Since(now)
|
||||
}
|
||||
mu.Unlock()
|
||||
log.Printf("node2.Lock:%v\n", time.Since(now2))
|
||||
})
|
||||
|
||||
time.Sleep(time.Millisecond * 24)
|
||||
now3 := time.Now()
|
||||
mu.Lock()
|
||||
node1.Stop()
|
||||
node2.Stop()
|
||||
isClosed = true
|
||||
close(tc)
|
||||
mu.Unlock()
|
||||
|
||||
log.Printf("node1.Stop:%v\n", time.Since(now3))
|
||||
cnt := 1
|
||||
for tv := range tc {
|
||||
left := time.Millisecond * 10 * time.Duration(cnt)
|
||||
right := time.Duration(cnt) * 2 * 10 * time.Millisecond
|
||||
if tv < left || tv > right {
|
||||
t.Errorf("index(%d) (%v)tc < %v || tc > %v", cnt, tv, left, right)
|
||||
}
|
||||
// cnt++
|
||||
}
|
||||
if atomic.LoadInt32(&count) != 2 {
|
||||
|
||||
t.Errorf("count:%d != 2", atomic.LoadInt32(&count))
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
t.Run("90ms", func(t *testing.T) {
|
||||
tm := NewTimer(WithMinHeap())
|
||||
go tm.Run()
|
||||
count := int32(0)
|
||||
tm.AfterFunc(time.Millisecond*90, func() { atomic.AddInt32(&count, 1) })
|
||||
tm.AfterFunc(time.Millisecond*90, func() { atomic.AddInt32(&count, 2) })
|
||||
|
||||
time.Sleep(time.Millisecond * 180)
|
||||
if atomic.LoadInt32(&count) != 3 {
|
||||
t.Errorf("count != 3")
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// 测试Schedule 运行的周期可对
|
||||
func Test_MinHeap_ScheduleFunc_Run(t *testing.T) {
|
||||
t.Run("1ms", func(t *testing.T) {
|
||||
tm := NewTimer(WithMinHeap())
|
||||
go tm.Run()
|
||||
count := int32(0)
|
||||
|
||||
_ = tm.ScheduleFunc(2*time.Millisecond, func() {
|
||||
log.Printf("%v\n", time.Now())
|
||||
atomic.AddInt32(&count, 1)
|
||||
if atomic.LoadInt32(&count) == 2 {
|
||||
tm.Stop()
|
||||
}
|
||||
})
|
||||
|
||||
time.Sleep(time.Millisecond * 5)
|
||||
if atomic.LoadInt32(&count) != 2 {
|
||||
t.Errorf("count:%d != 2", atomic.LoadInt32(&count))
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
t.Run("10ms", func(t *testing.T) {
|
||||
tm := NewTimer(WithMinHeap())
|
||||
go tm.Run()
|
||||
count := int32(0)
|
||||
tc := make(chan time.Duration, 2)
|
||||
var mu sync.Mutex
|
||||
isClosed := false
|
||||
now := time.Now()
|
||||
|
||||
node := tm.ScheduleFunc(time.Millisecond*10, func() {
|
||||
mu.Lock()
|
||||
atomic.AddInt32(&count, 1)
|
||||
|
||||
if atomic.LoadInt32(&count) <= 2 && !isClosed {
|
||||
tc <- time.Since(now)
|
||||
}
|
||||
mu.Unlock()
|
||||
})
|
||||
|
||||
time.Sleep(time.Millisecond * 25)
|
||||
|
||||
mu.Lock()
|
||||
close(tc)
|
||||
isClosed = true
|
||||
node.Stop()
|
||||
mu.Unlock()
|
||||
|
||||
cnt := 1
|
||||
for tv := range tc {
|
||||
left := time.Millisecond * 10 * time.Duration(cnt)
|
||||
right := time.Duration(cnt) * 2 * 10 * time.Millisecond
|
||||
if tv < left || tv > right {
|
||||
t.Errorf("index(%d) (%v)tc < %v || tc > %v", cnt, tv, left, right)
|
||||
}
|
||||
cnt++
|
||||
}
|
||||
|
||||
if atomic.LoadInt32(&count) != 2 {
|
||||
t.Errorf("count:%d != 2", atomic.LoadInt32(&count))
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
t.Run("30ms", func(t *testing.T) {
|
||||
tm := NewTimer(WithMinHeap())
|
||||
go tm.Run()
|
||||
count := int32(0)
|
||||
c := make(chan bool, 1)
|
||||
|
||||
node := tm.ScheduleFunc(time.Millisecond*30, func() {
|
||||
atomic.AddInt32(&count, 1)
|
||||
if atomic.LoadInt32(&count) == 2 {
|
||||
c <- true
|
||||
}
|
||||
})
|
||||
go func() {
|
||||
<-c
|
||||
node.Stop()
|
||||
}()
|
||||
|
||||
time.Sleep(time.Millisecond * 70)
|
||||
if atomic.LoadInt32(&count) != 2 {
|
||||
t.Errorf("count:%d != 2", atomic.LoadInt32(&count))
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// 测试Stop是否会等待正在运行的任务结束
|
||||
func Test_Run_Stop(t *testing.T) {
|
||||
t.Run("1ms", func(t *testing.T) {
|
||||
tm := NewTimer(WithMinHeap())
|
||||
count := uint32(0)
|
||||
tm.AfterFunc(time.Millisecond, func() { atomic.AddUint32(&count, 1) })
|
||||
tm.AfterFunc(time.Millisecond, func() { atomic.AddUint32(&count, 1) })
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 4)
|
||||
tm.Stop()
|
||||
}()
|
||||
tm.Run()
|
||||
if atomic.LoadUint32(&count) != 2 {
|
||||
t.Errorf("count != 2")
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
type curstomTest struct {
|
||||
count int32
|
||||
}
|
||||
|
||||
func (c *curstomTest) Next(now time.Time) (rv time.Time) {
|
||||
rv = now.Add(time.Duration(c.count) * time.Millisecond * 10)
|
||||
atomic.AddInt32(&c.count, 1)
|
||||
return
|
||||
}
|
||||
|
||||
// 验证自定义函数的运行间隔时间
|
||||
func Test_CustomFunc(t *testing.T) {
|
||||
t.Run("custom", func(t *testing.T) {
|
||||
tm := NewTimer(WithMinHeap())
|
||||
// mh := tm.(*minHeap) // 最小堆
|
||||
tc := make(chan time.Duration, 2)
|
||||
now := time.Now()
|
||||
count := uint32(1)
|
||||
stop := make(chan bool, 1)
|
||||
// 自定义函数
|
||||
node := tm.CustomFunc(&curstomTest{count: 1}, func() {
|
||||
|
||||
if atomic.LoadUint32(&count) == 2 {
|
||||
return
|
||||
}
|
||||
// 计算运行次数
|
||||
atomic.AddUint32(&count, 1)
|
||||
tc <- time.Since(now)
|
||||
// 关闭这个任务
|
||||
close(stop)
|
||||
})
|
||||
|
||||
go func() {
|
||||
<-stop
|
||||
node.Stop()
|
||||
tm.Stop()
|
||||
}()
|
||||
|
||||
tm.Run()
|
||||
close(tc)
|
||||
cnt := 1
|
||||
for tv := range tc {
|
||||
left := time.Millisecond * 10 * time.Duration(cnt)
|
||||
right := time.Duration(cnt) * 2 * 10 * time.Millisecond
|
||||
if tv < left || tv > right {
|
||||
t.Errorf("index(%d) (%v)tc < %v || tc > %v", cnt, tv, left, right)
|
||||
}
|
||||
cnt++
|
||||
}
|
||||
if atomic.LoadUint32(&count) != 2 {
|
||||
t.Errorf("count != 2")
|
||||
}
|
||||
|
||||
// 正在运行的任务是比较短暂的,所以外部很难
|
||||
// if mh.runCount != int32(1) {
|
||||
// t.Errorf("mh.runCount:%d != 1", mh.runCount)
|
||||
// }
|
||||
|
||||
})
|
||||
}
|
||||
|
||||
// 验证运行次数是符合预期的
|
||||
func Test_RunCount(t *testing.T) {
|
||||
t.Run("runcount-10ms", func(t *testing.T) {
|
||||
tm := NewTimer(WithMinHeap())
|
||||
max := 10
|
||||
go func() {
|
||||
tm.Run()
|
||||
}()
|
||||
|
||||
count := uint32(0)
|
||||
for i := 0; i < max; i++ {
|
||||
tm.ScheduleFunc(time.Millisecond*10, func() {
|
||||
atomic.AddUint32(&count, 1)
|
||||
})
|
||||
}
|
||||
|
||||
time.Sleep(time.Millisecond * 15)
|
||||
tm.Stop()
|
||||
if count != uint32(max) {
|
||||
t.Errorf("count:%d != %d", count, max)
|
||||
}
|
||||
|
||||
})
|
||||
}
|
||||
39
common/utils/timer/option.go
Normal file
39
common/utils/timer/option.go
Normal file
@@ -0,0 +1,39 @@
|
||||
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
|
||||
//
|
||||
// mit license
|
||||
package timer
|
||||
|
||||
type option struct {
|
||||
timeWheel bool
|
||||
minHeap bool
|
||||
skiplist bool
|
||||
rbtree bool
|
||||
}
|
||||
|
||||
type Option func(c *option)
|
||||
|
||||
func WithTimeWheel() Option {
|
||||
return func(o *option) {
|
||||
o.timeWheel = true
|
||||
}
|
||||
}
|
||||
|
||||
func WithMinHeap() Option {
|
||||
return func(o *option) {
|
||||
o.minHeap = true
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
func WithSkipList() Option {
|
||||
return func(o *option) {
|
||||
o.skiplist = true
|
||||
}
|
||||
}
|
||||
|
||||
// TODO
|
||||
func WithRbtree() Option {
|
||||
return func(o *option) {
|
||||
o.rbtree = true
|
||||
}
|
||||
}
|
||||
17
common/utils/timer/t_test.go
Normal file
17
common/utils/timer/t_test.go
Normal file
@@ -0,0 +1,17 @@
|
||||
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
|
||||
//
|
||||
// mit license
|
||||
package timer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func Test_Look(t *testing.T) {
|
||||
|
||||
tmp := newTimeHead(0, 0)
|
||||
offset := unsafe.Offsetof(tmp.Head)
|
||||
fmt.Printf("%d\n", offset)
|
||||
}
|
||||
294
common/utils/timer/time_wheel.go
Normal file
294
common/utils/timer/time_wheel.go
Normal file
@@ -0,0 +1,294 @@
|
||||
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
|
||||
//
|
||||
// mit license
|
||||
package timer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/antlabs/stl/list"
|
||||
)
|
||||
|
||||
const (
|
||||
nearShift = 8
|
||||
|
||||
nearSize = 1 << nearShift
|
||||
|
||||
levelShift = 6
|
||||
|
||||
levelSize = 1 << levelShift
|
||||
|
||||
nearMask = nearSize - 1
|
||||
|
||||
levelMask = levelSize - 1
|
||||
)
|
||||
|
||||
type timeWheel struct {
|
||||
// 单调递增累加值, 走过一个时间片就+1
|
||||
jiffies uint64
|
||||
|
||||
// 256个槽位
|
||||
t1 [nearSize]*Time
|
||||
|
||||
// 4个64槽位, 代表不同的刻度
|
||||
t2Tot5 [4][levelSize]*Time
|
||||
|
||||
// 时间只精确到10ms
|
||||
// curTimePoint 为1就是10ms 为2就是20ms
|
||||
curTimePoint time.Duration
|
||||
|
||||
// 上下文
|
||||
ctx context.Context
|
||||
|
||||
// 取消函数
|
||||
cancel context.CancelFunc
|
||||
}
|
||||
|
||||
func newTimeWheel() *timeWheel {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
t := &timeWheel{ctx: ctx, cancel: cancel}
|
||||
|
||||
t.init()
|
||||
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *timeWheel) init() {
|
||||
for i := 0; i < nearSize; i++ {
|
||||
t.t1[i] = newTimeHead(1, uint64(i))
|
||||
}
|
||||
|
||||
for i := 0; i < 4; i++ {
|
||||
for j := 0; j < levelSize; j++ {
|
||||
t.t2Tot5[i][j] = newTimeHead(uint64(i+2), uint64(j))
|
||||
}
|
||||
}
|
||||
|
||||
// t.curTimePoint = get10Ms()
|
||||
}
|
||||
|
||||
func maxVal() uint64 {
|
||||
return (1 << (nearShift + 4*levelShift)) - 1
|
||||
}
|
||||
|
||||
func levelMax(index int) uint64 {
|
||||
return 1 << (nearShift + index*levelShift)
|
||||
}
|
||||
|
||||
func (t *timeWheel) index(n int) uint64 {
|
||||
return (t.jiffies >> (nearShift + levelShift*n)) & levelMask
|
||||
}
|
||||
|
||||
func (t *timeWheel) add(node *timeNode, jiffies uint64) *timeNode {
|
||||
var head *Time
|
||||
expire := node.expire
|
||||
idx := expire - jiffies
|
||||
|
||||
level, index := uint64(1), uint64(0)
|
||||
|
||||
if idx < nearSize {
|
||||
|
||||
index = uint64(expire) & nearMask
|
||||
head = t.t1[index]
|
||||
|
||||
} else {
|
||||
|
||||
max := maxVal()
|
||||
for i := 0; i <= 3; i++ {
|
||||
|
||||
if idx > max {
|
||||
idx = max
|
||||
expire = idx + jiffies
|
||||
}
|
||||
|
||||
if uint64(idx) < levelMax(i+1) {
|
||||
index = uint64(expire >> (nearShift + i*levelShift) & levelMask)
|
||||
head = t.t2Tot5[i][index]
|
||||
level = uint64(i) + 2
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if head == nil {
|
||||
panic("not found head")
|
||||
}
|
||||
|
||||
head.lockPushBack(node, level, index)
|
||||
|
||||
return node
|
||||
}
|
||||
|
||||
func (t *timeWheel) AfterFunc(expire time.Duration, callback func()) TimeNoder {
|
||||
jiffies := atomic.LoadUint64(&t.jiffies)
|
||||
|
||||
expire = expire/(time.Millisecond*10) + time.Duration(jiffies)
|
||||
|
||||
node := &timeNode{
|
||||
expire: uint64(expire),
|
||||
callback: callback,
|
||||
root: t,
|
||||
}
|
||||
|
||||
return t.add(node, jiffies)
|
||||
}
|
||||
|
||||
func getExpire(expire time.Duration, jiffies uint64) time.Duration {
|
||||
return expire/(time.Millisecond*10) + time.Duration(jiffies)
|
||||
}
|
||||
|
||||
func (t *timeWheel) ScheduleFunc(userExpire time.Duration, callback func()) TimeNoder {
|
||||
jiffies := atomic.LoadUint64(&t.jiffies)
|
||||
|
||||
expire := getExpire(userExpire, jiffies)
|
||||
|
||||
node := &timeNode{
|
||||
userExpire: userExpire,
|
||||
expire: uint64(expire),
|
||||
callback: callback,
|
||||
isSchedule: true,
|
||||
root: t,
|
||||
}
|
||||
|
||||
return t.add(node, jiffies)
|
||||
}
|
||||
|
||||
func (t *timeWheel) Stop() {
|
||||
t.cancel()
|
||||
}
|
||||
|
||||
// 移动链表
|
||||
func (t *timeWheel) cascade(levelIndex int, index int) {
|
||||
tmp := newTimeHead(0, 0)
|
||||
|
||||
l := t.t2Tot5[levelIndex][index]
|
||||
l.Lock()
|
||||
if l.Len() == 0 {
|
||||
l.Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
l.ReplaceInit(&tmp.Head)
|
||||
|
||||
// 每次链表的元素被移动走,都修改version
|
||||
l.version.Add(1)
|
||||
l.Unlock()
|
||||
|
||||
offset := unsafe.Offsetof(tmp.Head)
|
||||
tmp.ForEachSafe(func(pos *list.Head) {
|
||||
node := (*timeNode)(pos.Entry(offset))
|
||||
t.add(node, atomic.LoadUint64(&t.jiffies))
|
||||
})
|
||||
}
|
||||
|
||||
// moveAndExec函数功能
|
||||
// 1. 先移动到near链表里面
|
||||
// 2. near链表节点为空时,从上一层里面移动一些节点到下一层
|
||||
// 3. 再执行
|
||||
func (t *timeWheel) moveAndExec() {
|
||||
// 这里时间溢出
|
||||
if uint32(t.jiffies) == 0 {
|
||||
// TODO
|
||||
// return
|
||||
}
|
||||
|
||||
// 如果本层的盘子没有定时器,这时候从上层的盘子移动一些过来
|
||||
index := t.jiffies & nearMask
|
||||
if index == 0 {
|
||||
for i := 0; i <= 3; i++ {
|
||||
index2 := t.index(i)
|
||||
t.cascade(i, int(index2))
|
||||
if index2 != 0 {
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
atomic.AddUint64(&t.jiffies, 1)
|
||||
|
||||
t.t1[index].Lock()
|
||||
if t.t1[index].Len() == 0 {
|
||||
t.t1[index].Unlock()
|
||||
return
|
||||
}
|
||||
|
||||
head := newTimeHead(0, 0)
|
||||
t1 := t.t1[index]
|
||||
t1.ReplaceInit(&head.Head)
|
||||
t1.version.Add(1)
|
||||
t.t1[index].Unlock()
|
||||
|
||||
// 执行,链表中的定时器
|
||||
offset := unsafe.Offsetof(head.Head)
|
||||
|
||||
head.ForEachSafe(func(pos *list.Head) {
|
||||
val := (*timeNode)(pos.Entry(offset))
|
||||
head.Del(pos)
|
||||
|
||||
if val.stop.Load() == haveStop {
|
||||
return
|
||||
}
|
||||
|
||||
go val.callback()
|
||||
|
||||
if val.isSchedule {
|
||||
jiffies := t.jiffies
|
||||
// 这里的jiffies必须要减去1
|
||||
// 当前的callback被调用,已经包含一个时间片,如果不把这个时间片减去,
|
||||
// 每次多一个时间片,就变成累加器, 最后周期定时器慢慢会变得不准
|
||||
val.expire = uint64(getExpire(val.userExpire, jiffies-1))
|
||||
t.add(val, jiffies)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
// get10Ms函数通过参数传递,为了方便测试
|
||||
func (t *timeWheel) run(get10Ms func() time.Duration) {
|
||||
// 先判断是否需要更新
|
||||
// 内核里面实现使用了全局jiffies和本地的jiffies比较,应用层没有jiffies,直接使用时间比较
|
||||
// 这也是skynet里面的做法
|
||||
|
||||
ms10 := get10Ms()
|
||||
|
||||
if ms10 < t.curTimePoint {
|
||||
|
||||
fmt.Printf("github.com/antlabs/timer:Time has been called back?from(%d)(%d)\n",
|
||||
ms10, t.curTimePoint)
|
||||
|
||||
t.curTimePoint = ms10
|
||||
return
|
||||
}
|
||||
|
||||
diff := ms10 - t.curTimePoint
|
||||
t.curTimePoint = ms10
|
||||
|
||||
for i := 0; i < int(diff); i++ {
|
||||
t.moveAndExec()
|
||||
}
|
||||
}
|
||||
|
||||
// 自定义, TODO
|
||||
func (t *timeWheel) CustomFunc(n Next, callback func()) TimeNoder {
|
||||
return &timeNode{}
|
||||
}
|
||||
|
||||
func (t *timeWheel) Run() {
|
||||
t.curTimePoint = get10Ms()
|
||||
// 10ms精度
|
||||
tk := time.NewTicker(time.Millisecond * 10)
|
||||
defer tk.Stop()
|
||||
|
||||
for {
|
||||
select {
|
||||
case <-tk.C:
|
||||
t.run(get10Ms)
|
||||
case <-t.ctx.Done():
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
114
common/utils/timer/time_wheel_node.go
Normal file
114
common/utils/timer/time_wheel_node.go
Normal file
@@ -0,0 +1,114 @@
|
||||
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
|
||||
//
|
||||
// mit license
|
||||
package timer
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
"unsafe"
|
||||
|
||||
"github.com/antlabs/stl/list"
|
||||
)
|
||||
|
||||
const (
|
||||
haveStop = uint32(1)
|
||||
)
|
||||
|
||||
// 先使用sync.Mutex实现功能
|
||||
// 后面使用cas优化
|
||||
type Time struct {
|
||||
timeNode
|
||||
sync.Mutex
|
||||
|
||||
// |---16bit---|---16bit---|------32bit-----|
|
||||
// |---level---|---index---|-------seq------|
|
||||
// level 在near盘子里就是1, 在T2ToTt[0]盘子里就是2起步
|
||||
// index 就是各自盘子的索引值
|
||||
// seq 自增id
|
||||
version atomic.Uint64
|
||||
}
|
||||
|
||||
func newTimeHead(level uint64, index uint64) *Time {
|
||||
head := &Time{}
|
||||
head.version.Store(genVersionHeight(level, index))
|
||||
head.Init()
|
||||
return head
|
||||
}
|
||||
|
||||
func genVersionHeight(level uint64, index uint64) uint64 {
|
||||
return level<<(32+16) | index<<32
|
||||
}
|
||||
|
||||
func (t *Time) lockPushBack(node *timeNode, level uint64, index uint64) {
|
||||
t.Lock()
|
||||
defer t.Unlock()
|
||||
if node.stop.Load() == haveStop {
|
||||
return
|
||||
}
|
||||
|
||||
t.AddTail(&node.Head)
|
||||
atomic.StorePointer(&node.list, unsafe.Pointer(t))
|
||||
//更新节点的version信息
|
||||
node.version.Store(t.version.Load())
|
||||
}
|
||||
|
||||
type timeNode struct {
|
||||
expire uint64
|
||||
userExpire time.Duration
|
||||
callback func()
|
||||
stop atomic.Uint32
|
||||
list unsafe.Pointer //存放表头信息
|
||||
version atomic.Uint64 //保存节点版本信息
|
||||
isSchedule bool
|
||||
root *timeWheel
|
||||
list.Head
|
||||
}
|
||||
|
||||
// 一个timeNode节点有4个状态
|
||||
// 1.存在于初始化链表中
|
||||
// 2.被移动到tmp链表
|
||||
// 3.1 和 3.2是if else的状态
|
||||
//
|
||||
// 3.1被移动到new链表
|
||||
// 3.2直接执行
|
||||
//
|
||||
// 1和3.1状态是没有问题的
|
||||
// 2和3.2状态会是没有锁保护下的操作,会有数据竞争
|
||||
func (t *timeNode) Stop() bool {
|
||||
|
||||
t.stop.Store(haveStop)
|
||||
|
||||
// 使用版本号算法让timeNode知道自己是否被移动了
|
||||
// timeNode的version和表头的version一样表示没有被移动可以直接删除
|
||||
// 如果不一样,可能在第2或者3.2状态,使用惰性删除
|
||||
cpyList := (*Time)(atomic.LoadPointer(&t.list))
|
||||
cpyList.Lock()
|
||||
defer cpyList.Unlock()
|
||||
if t.version.Load() != cpyList.version.Load() {
|
||||
return false
|
||||
}
|
||||
|
||||
cpyList.Del(&t.Head)
|
||||
return true
|
||||
}
|
||||
|
||||
// warning: 该函数目前没有稳定
|
||||
func (t *timeNode) Reset(expire time.Duration) bool {
|
||||
cpyList := (*Time)(atomic.LoadPointer(&t.list))
|
||||
cpyList.Lock()
|
||||
defer cpyList.Unlock()
|
||||
// TODO: 这里有一个问题,如果在执行Reset的时候,这个节点已经被移动到tmp链表
|
||||
// if atomic.LoadUint64(&t.version) != atomic.LoadUint64(&cpyList.version) {
|
||||
// return
|
||||
// }
|
||||
cpyList.Del(&t.Head)
|
||||
jiffies := atomic.LoadUint64(&t.root.jiffies)
|
||||
|
||||
expire = expire/(time.Millisecond*10) + time.Duration(jiffies)
|
||||
t.expire = uint64(expire)
|
||||
|
||||
t.root.add(t, jiffies)
|
||||
return true
|
||||
}
|
||||
189
common/utils/timer/time_wheel_test.go
Normal file
189
common/utils/timer/time_wheel_test.go
Normal file
@@ -0,0 +1,189 @@
|
||||
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
|
||||
//
|
||||
// mit license
|
||||
package timer
|
||||
|
||||
import (
|
||||
"context"
|
||||
"math"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_maxVal(t *testing.T) {
|
||||
|
||||
if maxVal() != uint64(math.MaxUint32) {
|
||||
t.Error("maxVal() != uint64(math.MaxUint32)")
|
||||
}
|
||||
}
|
||||
|
||||
func Test_LevelMax(t *testing.T) {
|
||||
if levelMax(1) != uint64(1<<(nearShift+levelShift)) {
|
||||
t.Error("levelMax(1) != uint64(1<<(nearShift+levelShift))")
|
||||
}
|
||||
|
||||
if levelMax(2) != uint64(1<<(nearShift+2*levelShift)) {
|
||||
t.Error("levelMax(2) != uint64(1<<(nearShift+2*levelShift))")
|
||||
}
|
||||
|
||||
if levelMax(3) != uint64(1<<(nearShift+3*levelShift)) {
|
||||
t.Error("levelMax(3) != uint64(1<<(nearShift+3*levelShift))")
|
||||
}
|
||||
|
||||
if levelMax(4) != uint64(1<<(nearShift+4*levelShift)) {
|
||||
t.Error("levelMax(4) != uint64(1<<(nearShift+4*levelShift))")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_GenVersion(t *testing.T) {
|
||||
if genVersionHeight(1, 0xf) != uint64(0x0001000f00000000) {
|
||||
t.Error("genVersionHeight(1, 0xf) != uint64(0x0001000f00000000)")
|
||||
}
|
||||
|
||||
if genVersionHeight(1, 64) != uint64(0x0001004000000000) {
|
||||
t.Error("genVersionHeight(2, 0xf) != uint64(0x0001004000000000)")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 测试1小时
|
||||
func Test_hour(t *testing.T) {
|
||||
tw := newTimeWheel()
|
||||
|
||||
testHour := new(bool)
|
||||
done := make(chan struct{}, 1)
|
||||
tw.AfterFunc(time.Hour, func() {
|
||||
*testHour = true
|
||||
done <- struct{}{}
|
||||
})
|
||||
|
||||
expire := getExpire(time.Hour, 0)
|
||||
for i := 0; i < int(expire)+10; i++ {
|
||||
get10Ms := func() time.Duration {
|
||||
return tw.curTimePoint + 1
|
||||
}
|
||||
tw.run(get10Ms)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-done:
|
||||
case <-time.After(time.Second / 100):
|
||||
}
|
||||
|
||||
if *testHour == false {
|
||||
t.Error("testHour == false")
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 测试周期性定时器, 5s
|
||||
func Test_ScheduleFunc_5s(t *testing.T) {
|
||||
tw := newTimeWheel()
|
||||
|
||||
var first5 int32
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
const total = int32(1000)
|
||||
|
||||
testTime := time.Second * 5
|
||||
|
||||
tw.ScheduleFunc(testTime, func() {
|
||||
atomic.AddInt32(&first5, 1)
|
||||
if atomic.LoadInt32(&first5) == total {
|
||||
cancel()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
expire := getExpire(testTime*time.Duration(total), 0)
|
||||
for i := 0; i <= int(expire)+10; i++ {
|
||||
get10Ms := func() time.Duration {
|
||||
return tw.curTimePoint + 1
|
||||
}
|
||||
tw.run(get10Ms)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-time.After(time.Second / 100):
|
||||
}
|
||||
|
||||
if total != first5 {
|
||||
t.Errorf("total:%d != first5:%d\n", total, first5)
|
||||
}
|
||||
}
|
||||
|
||||
// 测试周期性定时器, 1hour
|
||||
func Test_ScheduleFunc_hour(t *testing.T) {
|
||||
tw := newTimeWheel()
|
||||
|
||||
var first5 int32
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
const total = int32(100)
|
||||
testTime := time.Hour
|
||||
|
||||
tw.ScheduleFunc(testTime, func() {
|
||||
atomic.AddInt32(&first5, 1)
|
||||
if atomic.LoadInt32(&first5) == total {
|
||||
cancel()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
expire := getExpire(testTime*time.Duration(total), 0)
|
||||
for i := 0; i <= int(expire)+10; i++ {
|
||||
get10Ms := func() time.Duration {
|
||||
return tw.curTimePoint + 1
|
||||
}
|
||||
tw.run(get10Ms)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-time.After(time.Second / 100):
|
||||
}
|
||||
|
||||
if total != first5 {
|
||||
t.Errorf("total:%d != first5:%d\n", total, first5)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 测试周期性定时器, 1day
|
||||
func Test_ScheduleFunc_day(t *testing.T) {
|
||||
tw := newTimeWheel()
|
||||
|
||||
var first5 int32
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
|
||||
const total = int32(10)
|
||||
testTime := time.Hour * 24
|
||||
|
||||
tw.ScheduleFunc(testTime, func() {
|
||||
atomic.AddInt32(&first5, 1)
|
||||
if atomic.LoadInt32(&first5) == total {
|
||||
cancel()
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
expire := getExpire(testTime*time.Duration(total), 0)
|
||||
for i := 0; i <= int(expire)+10; i++ {
|
||||
get10Ms := func() time.Duration {
|
||||
return tw.curTimePoint + 1
|
||||
}
|
||||
tw.run(get10Ms)
|
||||
}
|
||||
|
||||
select {
|
||||
case <-ctx.Done():
|
||||
case <-time.After(time.Second / 100):
|
||||
}
|
||||
|
||||
if total != first5 {
|
||||
t.Errorf("total:%d != first5:%d\n", total, first5)
|
||||
}
|
||||
}
|
||||
10
common/utils/timer/time_wheel_utils.go
Normal file
10
common/utils/timer/time_wheel_utils.go
Normal file
@@ -0,0 +1,10 @@
|
||||
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
|
||||
//
|
||||
// mit license
|
||||
package timer
|
||||
|
||||
import "time"
|
||||
|
||||
func get10Ms() time.Duration {
|
||||
return time.Duration(int64(time.Now().UnixNano() / int64(time.Millisecond) / 10))
|
||||
}
|
||||
53
common/utils/timer/timer.go
Normal file
53
common/utils/timer/timer.go
Normal file
@@ -0,0 +1,53 @@
|
||||
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
|
||||
//
|
||||
// mit license
|
||||
package timer
|
||||
|
||||
import "time"
|
||||
|
||||
type Next interface {
|
||||
Next(time.Time) time.Time
|
||||
}
|
||||
|
||||
// 定时器接口
|
||||
type Timer interface {
|
||||
// 一次性定时器
|
||||
AfterFunc(expire time.Duration, callback func()) TimeNoder
|
||||
|
||||
// 周期性定时器
|
||||
ScheduleFunc(expire time.Duration, callback func()) TimeNoder
|
||||
|
||||
// 自定义下次的时间
|
||||
CustomFunc(n Next, callback func()) TimeNoder
|
||||
|
||||
// 运行
|
||||
Run()
|
||||
|
||||
// 停止所有定时器
|
||||
Stop()
|
||||
}
|
||||
|
||||
// 停止单个定时器
|
||||
type TimeNoder interface {
|
||||
Stop() bool
|
||||
// 重置时间器
|
||||
Reset(expire time.Duration) bool
|
||||
}
|
||||
|
||||
// 定时器构造函数
|
||||
func NewTimer(opt ...Option) Timer {
|
||||
var o option
|
||||
for _, cb := range opt {
|
||||
cb(&o)
|
||||
}
|
||||
|
||||
if o.timeWheel {
|
||||
return newTimeWheel()
|
||||
}
|
||||
|
||||
if o.minHeap {
|
||||
return newMinHeap()
|
||||
}
|
||||
|
||||
return newTimeWheel()
|
||||
}
|
||||
219
common/utils/timer/timer_test.go
Normal file
219
common/utils/timer/timer_test.go
Normal file
@@ -0,0 +1,219 @@
|
||||
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
|
||||
//
|
||||
// mit license
|
||||
package timer
|
||||
|
||||
import (
|
||||
"log"
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"testing"
|
||||
"time"
|
||||
)
|
||||
|
||||
func Test_ScheduleFunc(t *testing.T) {
|
||||
tm := NewTimer()
|
||||
|
||||
log.SetFlags(log.Ldate | log.Lmicroseconds)
|
||||
count := uint32(0)
|
||||
log.Printf("start\n")
|
||||
|
||||
tm.ScheduleFunc(time.Millisecond*100, func() {
|
||||
log.Printf("schedule\n")
|
||||
atomic.AddUint32(&count, 1)
|
||||
})
|
||||
|
||||
go func() {
|
||||
time.Sleep(570 * time.Millisecond)
|
||||
log.Printf("stop\n")
|
||||
tm.Stop()
|
||||
}()
|
||||
|
||||
tm.Run()
|
||||
if count != 5 {
|
||||
t.Errorf("count:%d != 5\n", count)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_AfterFunc(t *testing.T) {
|
||||
tm := NewTimer()
|
||||
go tm.Run()
|
||||
log.Printf("start\n")
|
||||
|
||||
count := uint32(0)
|
||||
tm.AfterFunc(time.Millisecond*20, func() {
|
||||
log.Printf("after Millisecond * 20")
|
||||
atomic.AddUint32(&count, 1)
|
||||
})
|
||||
|
||||
tm.AfterFunc(time.Second, func() {
|
||||
log.Printf("after second")
|
||||
atomic.AddUint32(&count, 1)
|
||||
})
|
||||
|
||||
/*
|
||||
tm.AfterFunc(time.Minute, func() {
|
||||
log.Printf("after Minute")
|
||||
})
|
||||
*/
|
||||
/*
|
||||
tm.AfterFunc(time.Hour, nil)
|
||||
tm.AfterFunc(time.Hour*24, nil)
|
||||
tm.AfterFunc(time.Hour*24*365, nil)
|
||||
tm.AfterFunc(time.Hour*24*365*12, nil)
|
||||
*/
|
||||
|
||||
time.Sleep(time.Second + time.Millisecond*100)
|
||||
tm.Stop()
|
||||
|
||||
if count != 2 {
|
||||
t.Errorf("count:%d != 2\n", count)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
func Test_Node_Stop_1(t *testing.T) {
|
||||
tm := NewTimer()
|
||||
count := uint32(0)
|
||||
node := tm.AfterFunc(time.Millisecond*10, func() {
|
||||
atomic.AddUint32(&count, 1)
|
||||
})
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 30)
|
||||
node.Stop()
|
||||
tm.Stop()
|
||||
}()
|
||||
|
||||
tm.Run()
|
||||
if count != 1 {
|
||||
t.Errorf("count:%d == 1\n", count)
|
||||
}
|
||||
}
|
||||
|
||||
func Test_Node_Stop(t *testing.T) {
|
||||
tm := NewTimer()
|
||||
count := uint32(0)
|
||||
node := tm.AfterFunc(time.Millisecond*100, func() {
|
||||
atomic.AddUint32(&count, 1)
|
||||
})
|
||||
node.Stop()
|
||||
go func() {
|
||||
time.Sleep(time.Millisecond * 200)
|
||||
tm.Stop()
|
||||
}()
|
||||
tm.Run()
|
||||
|
||||
if count == 1 {
|
||||
t.Errorf("count:%d == 1\n", count)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// 测试重置定时器
|
||||
func Test_Reset(t *testing.T) {
|
||||
t.Run("min heap reset", func(t *testing.T) {
|
||||
|
||||
tm := NewTimer(WithMinHeap())
|
||||
|
||||
go tm.Run()
|
||||
count := int32(0)
|
||||
|
||||
tc := make(chan time.Duration, 2)
|
||||
|
||||
var mu sync.Mutex
|
||||
isClose := false
|
||||
now := time.Now()
|
||||
node1 := tm.AfterFunc(time.Millisecond*100, func() {
|
||||
|
||||
mu.Lock()
|
||||
atomic.AddInt32(&count, 1)
|
||||
if atomic.LoadInt32(&count) <= 2 && !isClose {
|
||||
tc <- time.Since(now)
|
||||
}
|
||||
mu.Unlock()
|
||||
})
|
||||
|
||||
node2 := tm.AfterFunc(time.Millisecond*100, func() {
|
||||
mu.Lock()
|
||||
atomic.AddInt32(&count, 1)
|
||||
if atomic.LoadInt32(&count) <= 2 && !isClose {
|
||||
tc <- time.Since(now)
|
||||
}
|
||||
mu.Unlock()
|
||||
})
|
||||
node1.Reset(time.Millisecond)
|
||||
node2.Reset(time.Millisecond)
|
||||
|
||||
time.Sleep(time.Millisecond * 3)
|
||||
mu.Lock()
|
||||
isClose = true
|
||||
close(tc)
|
||||
node1.Stop()
|
||||
node2.Stop()
|
||||
mu.Unlock()
|
||||
for tv := range tc {
|
||||
if tv < time.Millisecond || tv > 2*time.Millisecond {
|
||||
t.Errorf("tc < time.Millisecond tc > 2*time.Millisecond")
|
||||
|
||||
}
|
||||
}
|
||||
if atomic.LoadInt32(&count) != 2 {
|
||||
t.Errorf("count:%d != 2", atomic.LoadInt32(&count))
|
||||
}
|
||||
|
||||
})
|
||||
|
||||
t.Run("time wheel reset", func(t *testing.T) {
|
||||
tm := NewTimer()
|
||||
|
||||
go func() {
|
||||
tm.Run()
|
||||
}()
|
||||
|
||||
count := int32(0)
|
||||
|
||||
tc := make(chan time.Duration, 2)
|
||||
|
||||
var mu sync.Mutex
|
||||
isClose := false
|
||||
now := time.Now()
|
||||
node1 := tm.AfterFunc(time.Millisecond*10, func() {
|
||||
|
||||
mu.Lock()
|
||||
atomic.AddInt32(&count, 1)
|
||||
if atomic.LoadInt32(&count) <= 2 && !isClose {
|
||||
tc <- time.Since(now)
|
||||
}
|
||||
mu.Unlock()
|
||||
})
|
||||
|
||||
node2 := tm.AfterFunc(time.Millisecond*10, func() {
|
||||
mu.Lock()
|
||||
atomic.AddInt32(&count, 1)
|
||||
if atomic.LoadInt32(&count) <= 2 && !isClose {
|
||||
tc <- time.Since(now)
|
||||
}
|
||||
mu.Unlock()
|
||||
})
|
||||
|
||||
node1.Reset(time.Millisecond * 20)
|
||||
node2.Reset(time.Millisecond * 20)
|
||||
|
||||
time.Sleep(time.Millisecond * 40)
|
||||
mu.Lock()
|
||||
isClose = true
|
||||
close(tc)
|
||||
node1.Stop()
|
||||
node2.Stop()
|
||||
mu.Unlock()
|
||||
for tv := range tc {
|
||||
if tv < time.Millisecond*20 || tv > 2*time.Millisecond*20 {
|
||||
t.Errorf("tc < time.Millisecond tc > 2*time.Millisecond")
|
||||
}
|
||||
}
|
||||
if atomic.LoadInt32(&count) != 2 {
|
||||
t.Errorf("count:%d != 2", atomic.LoadInt32(&count))
|
||||
}
|
||||
})
|
||||
}
|
||||
14
common/utils/timer/timer_wheel_utils_test.go
Normal file
14
common/utils/timer/timer_wheel_utils_test.go
Normal file
@@ -0,0 +1,14 @@
|
||||
// Copyright 2020-2024 guonaihong, antlabs. All rights reserved.
|
||||
//
|
||||
// mit license
|
||||
package timer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func Test_Get10Ms(t *testing.T) {
|
||||
|
||||
fmt.Printf("%v:%d", get10Ms(), get10Ms())
|
||||
}
|
||||
Reference in New Issue
Block a user