diff --git a/TODO.MD b/TODO.MD index 8a95494f7..98de17c6f 100644 --- a/TODO.MD +++ b/TODO.MD @@ -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类型的转换; diff --git a/g/os/gcron/gcron_entry.go b/g/os/gcron/gcron_entry.go index 665fec2d3..7992d99f3 100644 --- a/g/os/gcron/gcron_entry.go +++ b/g/os/gcron/gcron_entry.go @@ -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 diff --git a/g/os/gcron/gcron_unit_1_test.go b/g/os/gcron/gcron_unit_1_test.go index 9feab51b1..5191ba604 100644 --- a/g/os/gcron/gcron_unit_1_test.go +++ b/g/os/gcron/gcron_unit_1_test.go @@ -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) }) diff --git a/g/os/gcron/gcron_unit_2_test.go b/g/os/gcron/gcron_unit_2_test.go index 3a9a66e2a..e41ebf2ab 100644 --- a/g/os/gcron/gcron_unit_2_test.go +++ b/g/os/gcron/gcron_unit_2_test.go @@ -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) - }) } diff --git a/g/os/gtimer/gtimer.go b/g/os/gtimer/gtimer.go index 4fb706ee6..31902dc15 100644 --- a/g/os/gtimer/gtimer.go +++ b/g/os/gtimer/gtimer.go @@ -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) } diff --git a/g/os/gtimer/gtimer_entry.go b/g/os/gtimer/gtimer_entry.go index 26c3fb9ae..f99e11766 100644 --- a/g/os/gtimer/gtimer_entry.go +++ b/g/os/gtimer/gtimer_entry.go @@ -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 diff --git a/g/os/gtimer/gtimer_loop.go b/g/os/gtimer/gtimer_loop.go index f2e563e83..72a37e629 100644 --- a/g/os/gtimer/gtimer_loop.go +++ b/g/os/gtimer/gtimer_loop.go @@ -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) diff --git a/g/os/gtimer/gtimer_timer.go b/g/os/gtimer/gtimer_timer.go index 8710dda1b..a54a1d6f6 100644 --- a/g/os/gtimer/gtimer_timer.go +++ b/g/os/gtimer/gtimer_timer.go @@ -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 diff --git a/g/os/gtimer/gtimer_wheel.go b/g/os/gtimer/gtimer_wheel.go deleted file mode 100644 index eccc94fde..000000000 --- a/g/os/gtimer/gtimer_wheel.go +++ /dev/null @@ -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时间长度, 毫秒) -} diff --git a/g/os/gtimer/gtimer_z_unit_1_test.go b/g/os/gtimer/gtimer_z_unit_1_test.go index 073bb9fd8..1cc939399 100644 --- a/g/os/gtimer/gtimer_z_unit_1_test.go +++ b/g/os/gtimer/gtimer_z_unit_1_test.go @@ -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) }) } diff --git a/g/os/gtimer/gtimer_z_unit_2_test.go b/g/os/gtimer/gtimer_z_unit_2_test.go index 66a45880f..30b0ae9f4 100644 --- a/g/os/gtimer/gtimer_z_unit_2_test.go +++ b/g/os/gtimer/gtimer_z_unit_2_test.go @@ -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) } + diff --git a/g/os/gtimer/gtimer_z_unit_3_test.go b/g/os/gtimer/gtimer_z_unit_3_test.go deleted file mode 100644 index 04e592f4c..000000000 --- a/g/os/gtimer/gtimer_z_unit_3_test.go +++ /dev/null @@ -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) - }) -} diff --git a/g/util/gtest/gtest.go b/g/util/gtest/gtest.go index 4a59fe783..ae4b5f674 100644 --- a/g/util/gtest/gtest.go +++ b/g/util/gtest/gtest.go @@ -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(``); 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 } \ No newline at end of file