gcron, gtimer updates

This commit is contained in:
John
2019-01-22 13:50:10 +08:00
parent 24990e26c8
commit 5110313657
13 changed files with 242 additions and 155 deletions

View File

@ -46,7 +46,7 @@
1. grpool性能压测结果变慢的问题
1. 增加jumplist的数据结构容器
1. DelayQueue/PriorityQueue
1. gconv针对struct的转换增加json tag支持gconv.Map默认也支持json tag;
# DONE
1. gconv完善针对不同类型的判断例如尽量减少sprintf("%v", xxx)来执行string类型的转换

View File

@ -81,6 +81,7 @@ func (entry *Entry) Stop() {
// 关闭定时任务
func (entry *Entry) Close() {
entry.cron.Remove(entry.Name)
entry.entry.Close()
}
@ -92,6 +93,7 @@ func (entry *Entry) check() {
return
case STATUS_CLOSED:
entry.cron.Remove(entry.Name)
gtimer.Exit()
case STATUS_READY: fallthrough

View File

@ -72,6 +72,7 @@ func TestCron_Basic(t *testing.T) {
}
func TestCron_AddSingleton(t *testing.T) {
// un used, can be removed
gtest.Case(t, func() {
cron := gcron.New()
cron.Add("* * * * * *", func() {}, "add")
@ -88,7 +89,7 @@ func TestCron_AddSingleton(t *testing.T) {
gtest.AssertNE(entry1, nil)
gtest.Assert(entry2, nil)
})
// keep this
gtest.Case(t, func() {
cron := gcron.New()
array := garray.New(0, 0)
@ -193,7 +194,7 @@ func TestCron_DelayAddTimes(t *testing.T) {
time.Sleep(800*time.Millisecond)
gtest.Assert(array.Len(), 0)
gtest.Assert(cron.Size(), 1)
time.Sleep(5000*time.Millisecond)
time.Sleep(3000*time.Millisecond)
gtest.Assert(array.Len(), 2)
gtest.Assert(cron.Size(), 0)
})

View File

@ -10,6 +10,7 @@ package gcron_test
import (
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/g/os/gcron"
"gitee.com/johng/gf/g/os/glog"
"gitee.com/johng/gf/g/util/gtest"
"testing"
"time"
@ -17,9 +18,26 @@ import (
func TestCron_Entry_Operations(t *testing.T) {
gtest.Case(t, func() {
gtest.Case(t, func() {
cron := gcron.New()
array := garray.New(0, 0)
cron.DelayAddTimes(500*time.Millisecond, "* * * * * *", 2, func() {
array.Append(1)
})
gtest.Assert(cron.Size(), 0)
time.Sleep(800*time.Millisecond)
gtest.Assert(array.Len(), 0)
gtest.Assert(cron.Size(), 1)
time.Sleep(3000*time.Millisecond)
gtest.Assert(array.Len(), 2)
gtest.Assert(cron.Size(), 0)
})
cron := gcron.New()
array := garray.New(0, 0)
entry, err1 := cron.Add("* * * * * *", func() {
glog.Println("add")
array.Append(1)
})
gtest.Assert(err1, nil)
@ -29,16 +47,16 @@ func TestCron_Entry_Operations(t *testing.T) {
gtest.Assert(array.Len(), 1)
gtest.Assert(cron.Size(), 1)
entry.Stop()
time.Sleep(1200*time.Millisecond)
time.Sleep(2000*time.Millisecond)
gtest.Assert(array.Len(), 1)
gtest.Assert(cron.Size(), 1)
entry.Start()
time.Sleep(1200*time.Millisecond)
glog.Println("start")
time.Sleep(1000*time.Millisecond)
gtest.Assert(array.Len(), 2)
gtest.Assert(cron.Size(), 1)
entry.Close()
time.Sleep(1200*time.Millisecond)
gtest.Assert(cron.Size(), 0)
})
}

View File

@ -4,10 +4,10 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// Package gtimer implements Levelled Timing Wheel for interval/delayed jobs running and management.
// Package gtimer implements Hierarchical Timing Wheel for interval/delayed jobs running and management.
//
// 任务定时器(分层时间轮),
// 高效的时间轮任务管理模块,用于管理间隔/延迟运行任务。
// 任务定时器,
// 高性能的分层时间轮任务管理模块,用于管理间隔/延迟运行任务。
// 与gcron模块的区别是时间轮模块只管理间隔执行任务并且更注重执行效率(纳秒级别)。
// 需要注意执行时间间隔的准确性问题: https://github.com/golang/go/issues/14410
package gtimer
@ -30,47 +30,46 @@ const (
)
var (
// 默认的wheel管理对象
// 默认的wheel管理对象
defaultTimer = New(gDEFAULT_SLOT_NUMBER, gDEFAULT_WHEEL_INTERVAL, gDEFAULT_WHEEL_LEVEL)
)
// 类似与js中的SetTimeout一段时间后执行回调函数
// 类似与js中的SetTimeout一段时间后执行回调函数
func SetTimeout(delay time.Duration, job JobFunc) {
AddOnce(delay, job)
}
// 类似与js中的SetInterval每隔一段时间执行回调函数当回调函数返回true那么继续执行否则终止执行该方法是异步的
// 注意:由于采用的是循环而不是递归操作,因此间隔时间将会以上一次回调函数执行完成的时间来计算
// 类似与js中的SetInterval每隔一段时间执行指定回调函数。
func SetInterval(interval time.Duration, job JobFunc) {
Add(interval, job)
}
// 添加执行方法,可以给定名字,以便于后续执行删除
// 添加执行方法
func Add(interval time.Duration, job JobFunc) *Entry {
return defaultTimer.Add(interval, job)
}
// 添加执行方法,可以给定名字,以便于后续执行删除
// 添加执行方法,更多参数控制。
func AddEntry(interval time.Duration, job JobFunc, singleton bool, times int) *Entry {
return defaultTimer.AddEntry(interval, job, singleton, times)
}
// 添加单例运行循环任务
// 添加单例运行循环任务
func AddSingleton(interval time.Duration, job JobFunc) *Entry {
return defaultTimer.AddSingleton(interval, job)
}
// 添加只运行一次的循环任务
// 添加只运行一次的循环任务
func AddOnce(interval time.Duration, job JobFunc) *Entry {
return defaultTimer.AddOnce(interval, job)
}
// 添加运行指定次数的循环任务
// 添加运行指定次数的循环任务
func AddTimes(interval time.Duration, times int, job JobFunc) *Entry {
return defaultTimer.AddTimes(interval, times, job)
}
// 延迟添加循环任务delay参数单位为秒
// 延迟添加循环任务
func DelayAdd(delay time.Duration, interval time.Duration, job JobFunc) {
defaultTimer.DelayAdd(delay, interval, job)
}
@ -95,7 +94,7 @@ func DelayAddTimes(delay time.Duration, interval time.Duration, times int, job J
defaultTimer.DelayAddTimes(delay, interval, times, job)
}
// 在Job方法中调用停止当前运行的任务
// 在Job方法中调用停止当前运行的任务
func Exit() {
panic(gPANIC_EXIT)
}

View File

@ -28,7 +28,7 @@ type Entry struct {
// 任务执行方法
type JobFunc = func()
// 创建定时任务
// 创建定时任务
func (w *wheel) addEntry(interval time.Duration, job JobFunc, singleton bool, times int) *Entry {
ms := interval.Nanoseconds()/1e6
num := ms/w.intervalMs
@ -56,10 +56,9 @@ func (w *wheel) addEntry(interval time.Duration, job JobFunc, singleton bool, ti
return entry
}
// 创建定时任务
func (w *wheel) addEntryByParent(interval time.Duration, parent *Entry) *Entry {
ms := interval.Nanoseconds()/1e6
num := ms/w.intervalMs
// 创建定时任务给定父级Entry, 间隔参数参数为毫秒数.
func (w *wheel) addEntryByParent(interval int64, parent *Entry) *Entry {
num := interval/w.intervalMs
if num == 0 {
num = 1
}
@ -74,7 +73,7 @@ func (w *wheel) addEntryByParent(interval time.Duration, parent *Entry) *Entry {
interval : num,
singleton : parent.singleton,
createMs : nowMs,
intervalMs : ms,
intervalMs : interval,
rawIntervalMs : parent.rawIntervalMs,
}
w.slots[(ticks + num) % w.number].PushBack(entry)
@ -126,21 +125,20 @@ func (entry *Entry) Run() {
entry.job()
}
// 检测当前任务是否可运行, 参数为当前时间的纳秒数, 精度更高
// 检测当前任务是否可运行
func (entry *Entry) check(nowTicks int64, nowMs int64) bool {
switch entry.status.Val() {
case STATUS_STOPPED: fallthrough
case STATUS_CLOSED:
return false
}
// 时间轮客户端判断,是否满足运行刻度条件,误差会比较大
if diff := nowTicks - entry.create; diff > 0 && diff%entry.interval == 0 {
// 分层转换处理
if entry.wheel.level > 0 {
diffMs := nowMs - entry.createMs
//fmt.Println("diffMs:", entry.wheel.level, diffMs, entry.intervalMs, entry.rawIntervalMs)
switch {
// 表示新增
// 表示新增(当添加任务后在下一时间轮刻度马上触发)
case diffMs < entry.wheel.timer.intervalMs:
entry.wheel.slots[(nowTicks+entry.interval)%entry.wheel.number].PushBack(entry)
return false
@ -149,23 +147,15 @@ func (entry *Entry) check(nowTicks int64, nowMs int64) bool {
case diffMs >= entry.wheel.timer.intervalMs:
// 任务是否有必要进行分层转换
if leftMs := entry.intervalMs - diffMs; leftMs > entry.wheel.timer.intervalMs {
//fmt.Println("leveled",
// entry.wheel.level,
// entry.wheel.ticks.Val(),
// entry.create,
// diffMs,
// leftMs,
// entry.rawIntervalMs,
//)
// 往底层添加
entry.wheel.timer.doAddEntryByParent(time.Duration(leftMs)*time.Millisecond, entry)
// 往底层添加,通过毫秒计算并重新添加任务到对应的时间轮上,减小运行误差
entry.wheel.timer.doAddEntryByParent(leftMs, entry)
return false
}
}
}
// 是否单例
if entry.IsSingleton() {
//fmt.Println("IsSingleton", entry.status.Val(), entry.intervalMs)
// 注意原子操作结果判断
if entry.status.Set(STATUS_RUNNING) == STATUS_RUNNING {
return false
}
@ -173,7 +163,7 @@ func (entry *Entry) check(nowTicks int64, nowMs int64) bool {
// 次数限制
times := entry.times.Add(-1)
if times <= 0 {
// 注意原子操作
// 注意原子操作结果判断
if entry.status.Set(STATUS_CLOSED) == STATUS_CLOSED || times < 0 {
return false
}
@ -183,7 +173,6 @@ func (entry *Entry) check(nowTicks int64, nowMs int64) bool {
times = gDEFAULT_TIMES
entry.times.Set(gDEFAULT_TIMES)
}
return true
}
return false

View File

@ -38,12 +38,7 @@ func (w *wheel) proceed() {
n := w.ticks.Add(1)
l := w.slots[int(n%w.number)]
length := l.Len()
//if w.level > 0 {
// fmt.Println("loop:", w.level, w.ticks.Val(), time.Now().String())
//}
if length > 0 {
go func(l *glist.List, nowTicks int64) {
entry := (*Entry)(nil)
nowMs := time.Now().UnixNano()/1e6
@ -53,10 +48,6 @@ func (w *wheel) proceed() {
} else {
entry = v.(*Entry)
}
//fmt.Println(w.level, w.ticks.Val(), entry.create, entry.rawIntervalMs)
if entry.Status() == STATUS_CLOSED {
continue
}
// 是否满足运行条件
if entry.check(nowTicks, nowMs) {
// 异步执行运行
@ -76,9 +67,9 @@ func (w *wheel) proceed() {
entry.job()
}(entry)
}
// 是否继续添运行
// 是否继续添运行, 滚动任务
if entry.status.Val() != STATUS_CLOSED {
entry.wheel.timer.doAddEntryByParent(time.Duration(entry.rawIntervalMs)*time.Millisecond, entry)
entry.wheel.timer.doAddEntryByParent(entry.rawIntervalMs, entry)
}
}
}(l, n)

View File

@ -14,13 +14,25 @@ import (
// 定时器/分层时间轮
type Timer struct {
status *gtype.Int // 状态
wheels []*wheel // 分层
length int // 层数
status *gtype.Int // 定时器状态
wheels []*wheel // 分层时间轮对象
length int // 分层层数
number int // 每一层Slot Number
intervalMs int64 // 最小时间刻度(毫秒)
}
// 单层时间轮
type wheel struct {
timer *Timer // 所属定时器
level int // 所属分层索引号
slots []*glist.List // 所有的循环任务项, 按照Slot Number进行分组
number int64 // Slot Number=len(slots)
ticks *gtype.Int64 // 当前时间轮已转动的刻度数量
totalMs int64 // 整个时间轮的时间长度(毫秒)=number*interval
createMs int64 // 创建时间(毫秒)
intervalMs int64 // 时间间隔(slot时间长度, 毫秒)
}
// 创建分层时间轮
func New(slot int, interval time.Duration, level...int) *Timer {
length := gDEFAULT_WHEEL_LEVEL
@ -146,16 +158,16 @@ func (t *Timer) doAddEntry(interval time.Duration, job JobFunc, singleton bool,
return t.wheels[t.getLevelByIntervalMs(interval.Nanoseconds()/1e6)].addEntry(interval, job, singleton, times)
}
// 添加定时任务
func (t *Timer) doAddEntryByParent(interval time.Duration, parent *Entry) *Entry {
return t.wheels[t.getLevelByIntervalMs(interval.Nanoseconds()/1e6)].addEntryByParent(interval, parent)
// 添加定时任务给定父级Entry, 间隔参数参数为毫秒数.
func (t *Timer) doAddEntryByParent(interval int64, parent *Entry) *Entry {
return t.wheels[t.getLevelByIntervalMs(interval)].addEntryByParent(interval, parent)
}
// 根据intervalMs计算添加的分层索引
func (t *Timer) getLevelByIntervalMs(intervalMs int64) int {
pos, cmp := t.binSearchIndex(intervalMs)
switch cmp {
// intervalMs与最后匹配值相等
// intervalMs与最后匹配值相等, 不添加到匹配得层,而是向下一层添加
case 0: fallthrough
// intervalMs比最后匹配值小
case -1:
@ -180,7 +192,7 @@ func (t *Timer) getLevelByIntervalMs(intervalMs int64) int {
return 0
}
// 二分查找当前任务可以添加的时间轮对象索引
// 二分查找当前任务可以添加的时间轮对象索引.
func (t *Timer) binSearchIndex(n int64)(index int, result int) {
min := 0
max := t.length - 1

View File

@ -1,24 +0,0 @@
// Copyright 2019 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
package gtimer
import (
"gitee.com/johng/gf/g/container/glist"
"gitee.com/johng/gf/g/container/gtype"
)
// 单层时间轮
type wheel struct {
timer *Timer // 所属定时器
level int // 所属分层索引号
slots []*glist.List // 所有的循环任务项, 按照Slot Number进行分组
number int64 // Slot Number
ticks *gtype.Int64 // 当前时间轮已转动的刻度数量
totalMs int64 // 整个时间轮的时间长度(毫秒)=number*interval
createMs int64 // 创建时间(毫秒)
intervalMs int64 // 时间间隔(slot时间长度, 毫秒)
}

View File

@ -4,7 +4,7 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// 包方法操作
// Timer Operations
package gtimer_test
@ -21,20 +21,42 @@ func New() *gtimer.Timer {
return gtimer.New(10, 10*time.Millisecond)
}
func TestSetTimeout(t *testing.T) {
gtest.Case(t, func() {
array := garray.New(0, 0)
gtimer.SetTimeout(200*time.Millisecond, func() {
array.Append(1)
})
time.Sleep(1000*time.Millisecond)
gtest.Assert(array.Len(), 1)
})
}
func TestSetInterval(t *testing.T) {
gtest.Case(t, func() {
array := garray.New(0, 0)
gtimer.SetInterval(200*time.Millisecond, func() {
array.Append(1)
})
time.Sleep(1100*time.Millisecond)
gtest.Assert(array.Len(), 5)
})
}
func TestTimer_Add_Close(t *testing.T) {
gtest.Case(t, func() {
wheel := New()
timer := New()
array := garray.New(0, 0)
//fmt.Println("start", time.Now())
wheel.Add(time.Second, func() {
timer.Add(time.Second, func() {
//fmt.Println("entry1", time.Now())
array.Append(1)
})
wheel.Add(time.Second, func() {
timer.Add(time.Second, func() {
//fmt.Println("entry2", time.Now())
array.Append(1)
})
wheel.Add(2*time.Second, func() {
timer.Add(2*time.Second, func() {
//fmt.Println("entry3", time.Now())
array.Append(1)
})
@ -42,7 +64,7 @@ func TestTimer_Add_Close(t *testing.T) {
gtest.Assert(array.Len(), 2)
time.Sleep(1300*time.Millisecond)
gtest.Assert(array.Len(), 5)
wheel.Close()
timer.Close()
time.Sleep(1200*time.Millisecond)
fixedLength := array.Len()
time.Sleep(1200*time.Millisecond)
@ -50,11 +72,34 @@ func TestTimer_Add_Close(t *testing.T) {
})
}
func TestTimer_Singleton(t *testing.T) {
func TestTimer_Start_Stop_Close(t *testing.T) {
gtest.Case(t, func() {
timer := New()
array := garray.New(0, 0)
timer.Add(200*time.Millisecond, func() {
//glog.Println("add...")
array.Append(1)
})
gtest.Assert(array.Len(), 0)
time.Sleep(300*time.Millisecond)
gtest.Assert(array.Len(), 1)
timer.Stop()
time.Sleep(1000*time.Millisecond)
gtest.Assert(array.Len(), 1)
timer.Start()
time.Sleep(200*time.Millisecond)
gtest.Assert(array.Len(), 2)
timer.Close()
time.Sleep(1000*time.Millisecond)
gtest.Assert(array.Len(), 2)
})
}
func TestTimer_AddSingleton(t *testing.T) {
gtest.Case(t, func() {
wheel := New()
timer := New()
array := garray.New(0, 0)
wheel.AddSingleton(time.Second, func() {
timer.AddSingleton(time.Second, func() {
array.Append(1)
time.Sleep(10*time.Second)
})
@ -66,21 +111,21 @@ func TestTimer_Singleton(t *testing.T) {
})
}
func TestTimer_Once(t *testing.T) {
func TestTimer_AddOnce(t *testing.T) {
gtest.Case(t, func() {
wheel := New()
timer := New()
array := garray.New(0, 0)
wheel.AddOnce(time.Second, func() {
timer.AddOnce(time.Second, func() {
array.Append(1)
})
wheel.AddOnce(time.Second, func() {
timer.AddOnce(time.Second, func() {
array.Append(1)
})
time.Sleep(1200*time.Millisecond)
gtest.Assert(array.Len(), 2)
time.Sleep(1200*time.Millisecond)
gtest.Assert(array.Len(), 2)
wheel.Close()
timer.Close()
time.Sleep(1200*time.Millisecond)
fixedLength := array.Len()
time.Sleep(1200*time.Millisecond)
@ -88,11 +133,23 @@ func TestTimer_Once(t *testing.T) {
})
}
func TestTimer_AddTimes(t *testing.T) {
gtest.Case(t, func() {
timer := New()
array := garray.New(0, 0)
timer.AddTimes(time.Second, 2, func() {
array.Append(1)
})
time.Sleep(3500*time.Millisecond)
gtest.Assert(array.Len(), 2)
})
}
func TestTimer_DelayAdd(t *testing.T) {
gtest.Case(t, func() {
wheel := New()
timer := New()
array := garray.New(0, 0)
wheel.DelayAdd(time.Second, time.Second, func() {
timer.DelayAdd(time.Second, time.Second, func() {
array.Append(1)
})
time.Sleep(1200*time.Millisecond)
@ -102,11 +159,11 @@ func TestTimer_DelayAdd(t *testing.T) {
})
}
func TestTimer_DelayAdd_Singleton(t *testing.T) {
func TestTimer_DelayAddSingleton(t *testing.T) {
gtest.Case(t, func() {
wheel := New()
timer := New()
array := garray.New(0, 0)
wheel.DelayAddSingleton(time.Second, time.Second, func() {
timer.DelayAddSingleton(time.Second, time.Second, func() {
array.Append(1)
time.Sleep(10*time.Second)
})
@ -118,11 +175,11 @@ func TestTimer_DelayAdd_Singleton(t *testing.T) {
})
}
func TestTimer_DelayAdd_Once(t *testing.T) {
func TestTimer_DelayAddOnce(t *testing.T) {
gtest.Case(t, func() {
wheel := New()
timer := New()
array := garray.New(0, 0)
wheel.DelayAddOnce(time.Second, time.Second, func() {
timer.DelayAddOnce(time.Second, time.Second, func() {
array.Append(1)
})
time.Sleep(1200*time.Millisecond)
@ -136,15 +193,36 @@ func TestTimer_DelayAdd_Once(t *testing.T) {
})
}
func TestTimer_ExitJob(t *testing.T) {
func TestTimer_DelayAddTimes(t *testing.T) {
gtest.Case(t, func() {
timer := New()
array := garray.New(0, 0)
timer.DelayAddTimes(200*time.Millisecond, 500*time.Millisecond, 2, func() {
array.Append(1)
})
time.Sleep(200*time.Millisecond)
gtest.Assert(array.Len(), 0)
time.Sleep(600*time.Millisecond)
gtest.Assert(array.Len(), 1)
time.Sleep(600*time.Millisecond)
gtest.Assert(array.Len(), 2)
time.Sleep(1000*time.Millisecond)
gtest.Assert(array.Len(), 2)
})
}
func TestTimer_Exit(t *testing.T) {
gtest.Case(t, func() {
wheel := New()
timer := New()
array := garray.New(0, 0)
wheel.Add(time.Second, func() {
timer.Add(200*time.Millisecond, func() {
array.Append(1)
gtimer.Exit()
})
time.Sleep(1200*time.Millisecond)
time.Sleep(1000*time.Millisecond)
gtest.Assert(array.Len(), 1)
})
}

View File

@ -4,21 +4,22 @@
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// Entry操作
// Entry Operations
package gtimer_test
import (
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/g/os/gtimer"
"gitee.com/johng/gf/g/util/gtest"
"testing"
"time"
)
func TestTimer_Entry_Operation(t *testing.T) {
wheel := New()
func TestEntry_Start_Stop_Close(t *testing.T) {
timer := New()
array := garray.New(0, 0)
entry := wheel.Add(time.Second, func() {
entry := timer.Add(time.Second, func() {
array.Append(1)
})
time.Sleep(1200*time.Millisecond)
@ -32,16 +33,20 @@ func TestTimer_Entry_Operation(t *testing.T) {
entry.Close()
time.Sleep(1200*time.Millisecond)
gtest.Assert(array.Len(), 2)
gtest.Assert(entry.Status(), gtimer.STATUS_CLOSED)
}
func TestTimer_Entry_Singleton(t *testing.T) {
wheel := New()
array := garray.New(0, 0)
entry := wheel.Add(time.Second, func() {
func TestEntry_Singleton(t *testing.T) {
timer := New()
array := garray.New(0, 0)
entry := timer.Add(time.Second, func() {
array.Append(1)
time.Sleep(10*time.Second)
})
gtest.Assert(entry.IsSingleton(), false)
entry.SetSingleton(true)
gtest.Assert(entry.IsSingleton(), true)
time.Sleep(1200*time.Millisecond)
gtest.Assert(array.Len(), 1)
@ -49,13 +54,14 @@ func TestTimer_Entry_Singleton(t *testing.T) {
gtest.Assert(array.Len(), 1)
}
func TestTimer_Entry_Once(t *testing.T) {
wheel := New()
func TestEntry_SetTimes(t *testing.T) {
timer := New()
array := garray.New(0, 0)
entry := wheel.Add(time.Second, func() {
entry := timer.Add(200*time.Millisecond, func() {
array.Append(1)
})
entry.SetTimes(1)
entry.SetTimes(2)
time.Sleep(1200*time.Millisecond)
gtest.Assert(array.Len(), 1)
gtest.Assert(array.Len(), 2)
}

View File

@ -1,28 +0,0 @@
// Copyright 2018 gf Author(https://gitee.com/johng/gf). All Rights Reserved.
//
// This Source Code Form is subject to the terms of the MIT License.
// If a copy of the MIT was not distributed with this file,
// You can obtain one at https://gitee.com/johng/gf.
// 指定次数运行测试
package gtimer_test
import (
"gitee.com/johng/gf/g/container/garray"
"gitee.com/johng/gf/g/util/gtest"
"testing"
"time"
)
func TestTimer_Times(t *testing.T) {
gtest.Case(t, func() {
wheel := New()
array := garray.New(0, 0)
wheel.AddTimes(time.Second, 2, func() {
array.Append(1)
})
time.Sleep(3500*time.Millisecond)
gtest.Assert(array.Len(), 2)
})
}

View File

@ -15,6 +15,8 @@ import (
"gitee.com/johng/gf/g/util/gconv"
"os"
"reflect"
"regexp"
"runtime"
"testing"
)
@ -22,8 +24,7 @@ import (
func Case(t *testing.T, f func()) {
defer func() {
if err := recover(); err != nil {
glog.To(os.Stderr).Println(err)
glog.Header(false).PrintBacktrace(2)
fmt.Fprintf(os.Stderr, "%v\n%s", err, getBacktrace())
t.Fail()
}
}()
@ -205,4 +206,46 @@ func Fatal(message...interface{}) {
glog.To(os.Stderr).Println(`[FATAL]`, fmt.Sprint(message...))
glog.Header(false).PrintBacktrace(1)
os.Exit(1)
}
// 获取文件调用回溯字符串参数skip表示调用端往上多少级开始回溯
func getBacktrace(skip...int) string {
customSkip := 0
if len(skip) > 0 {
customSkip = skip[0]
}
backtrace := ""
index := 1
from := 0
// 首先定位业务文件开始位置
for i := 0; i < 10; i++ {
if _, file, _, ok := runtime.Caller(i); ok {
if reg, _ := regexp.Compile("/g/util/gtest/.+$"); !reg.MatchString(file) {
from = i
break
}
}
}
// 从业务文件开始位置根据自定义的skip开始backtrace
goRoot := runtime.GOROOT()
for i := from + customSkip; i < 10000; i++ {
if _, file, cline, ok := runtime.Caller(i); ok && file != "" {
if reg, _ := regexp.Compile(`<autogenerated>`); reg.MatchString(file) {
continue
}
if reg, _ := regexp.Compile("/g/util/gtest/.+$"); reg.MatchString(file) {
continue
}
if goRoot != "" {
if reg, _ := regexp.Compile("^" + goRoot); reg.MatchString(file) {
continue
}
}
backtrace += fmt.Sprintf(`%d. %s:%d%s`, index, file, cline, "\n")
index++
} else {
break
}
}
return backtrace
}