mirror of
https://gitee.com/johng/gf
synced 2026-06-06 02:25:47 +08:00
refract package gtimer for more stable
This commit is contained in:
@ -129,7 +129,7 @@ func (q *Queue) Close() {
|
||||
|
||||
// Len returns the length of the queue.
|
||||
// Note that the result might not be accurate as there's a
|
||||
// asynchronize channel reading the list constantly.
|
||||
// asynchronous channel reading the list constantly.
|
||||
func (q *Queue) Len() (length int) {
|
||||
if q.list != nil {
|
||||
length += q.list.Len()
|
||||
|
||||
@ -78,7 +78,7 @@ func serverProcessInit() {
|
||||
|
||||
// It's an ugly calling for better initializing the main package path
|
||||
// in source development environment. It is useful only be used in main goroutine.
|
||||
// It fails retrieving the main package path in asynchronized goroutines.
|
||||
// It fails retrieving the main package path in asynchronous goroutines.
|
||||
gfile.MainPkgPath()
|
||||
}
|
||||
|
||||
@ -416,7 +416,7 @@ func (s *Server) startServer(fdMap listenerFdMap) {
|
||||
s.servers = append(s.servers, s.newGracefulServer(itemFunc))
|
||||
}
|
||||
}
|
||||
// Start listening asynchronizedly.
|
||||
// Start listening asynchronously.
|
||||
serverRunning.Add(1)
|
||||
for _, v := range s.servers {
|
||||
go func(server *gracefulServer) {
|
||||
|
||||
@ -50,7 +50,7 @@ func GetLogLevel() int {
|
||||
// Add adds a timed task to default cron object.
|
||||
// A unique <name> can be bound with the timed task.
|
||||
// It returns and error if the <name> is already used.
|
||||
func Add(pattern string, job func(), name ...string) (*Entry, error) {
|
||||
func Add(pattern string, job func(), name ...string) (*Job, error) {
|
||||
return defaultCron.Add(pattern, job, name...)
|
||||
}
|
||||
|
||||
@ -58,21 +58,21 @@ func Add(pattern string, job func(), name ...string) (*Entry, error) {
|
||||
// A singleton timed task is that can only be running one single instance at the same time.
|
||||
// A unique <name> can be bound with the timed task.
|
||||
// It returns and error if the <name> is already used.
|
||||
func AddSingleton(pattern string, job func(), name ...string) (*Entry, error) {
|
||||
func AddSingleton(pattern string, job func(), name ...string) (*Job, error) {
|
||||
return defaultCron.AddSingleton(pattern, job, name...)
|
||||
}
|
||||
|
||||
// AddOnce adds a timed task which can be run only once, to default cron object.
|
||||
// A unique <name> can be bound with the timed task.
|
||||
// It returns and error if the <name> is already used.
|
||||
func AddOnce(pattern string, job func(), name ...string) (*Entry, error) {
|
||||
func AddOnce(pattern string, job func(), name ...string) (*Job, error) {
|
||||
return defaultCron.AddOnce(pattern, job, name...)
|
||||
}
|
||||
|
||||
// AddTimes adds a timed task which can be run specified times, to default cron object.
|
||||
// A unique <name> can be bound with the timed task.
|
||||
// It returns and error if the <name> is already used.
|
||||
func AddTimes(pattern string, times int, job func(), name ...string) (*Entry, error) {
|
||||
func AddTimes(pattern string, times int, job func(), name ...string) (*Job, error) {
|
||||
return defaultCron.AddTimes(pattern, times, job, name...)
|
||||
}
|
||||
|
||||
@ -100,7 +100,7 @@ func DelayAddTimes(delay time.Duration, pattern string, times int, job func(), n
|
||||
|
||||
// Search returns a scheduled task with the specified <name>.
|
||||
// It returns nil if no found.
|
||||
func Search(name string) *Entry {
|
||||
func Search(name string) *Job {
|
||||
return defaultCron.Search(name)
|
||||
}
|
||||
|
||||
@ -115,7 +115,7 @@ func Size() int {
|
||||
}
|
||||
|
||||
// Entries return all timed tasks as slice.
|
||||
func Entries() []*Entry {
|
||||
func Entries() []*Job {
|
||||
return defaultCron.Entries()
|
||||
}
|
||||
|
||||
|
||||
@ -60,20 +60,20 @@ func (c *Cron) GetLogLevel() int {
|
||||
// Add adds a timed task.
|
||||
// A unique <name> can be bound with the timed task.
|
||||
// It returns and error if the <name> is already used.
|
||||
func (c *Cron) Add(pattern string, job func(), name ...string) (*Entry, error) {
|
||||
func (c *Cron) Add(pattern string, job func(), name ...string) (*Job, error) {
|
||||
if len(name) > 0 {
|
||||
if c.Search(name[0]) != nil {
|
||||
return nil, errors.New(fmt.Sprintf(`cron job "%s" already exists`, name[0]))
|
||||
}
|
||||
}
|
||||
return c.addEntry(pattern, job, false, name...)
|
||||
return c.addJob(pattern, job, false, name...)
|
||||
}
|
||||
|
||||
// AddSingleton adds a singleton timed task.
|
||||
// A singleton timed task is that can only be running one single instance at the same time.
|
||||
// A unique <name> can be bound with the timed task.
|
||||
// It returns and error if the <name> is already used.
|
||||
func (c *Cron) AddSingleton(pattern string, job func(), name ...string) (*Entry, error) {
|
||||
func (c *Cron) AddSingleton(pattern string, job func(), name ...string) (*Job, error) {
|
||||
if entry, err := c.Add(pattern, job, name...); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
@ -85,7 +85,7 @@ func (c *Cron) AddSingleton(pattern string, job func(), name ...string) (*Entry,
|
||||
// AddOnce adds a timed task which can be run only once.
|
||||
// A unique <name> can be bound with the timed task.
|
||||
// It returns and error if the <name> is already used.
|
||||
func (c *Cron) AddOnce(pattern string, job func(), name ...string) (*Entry, error) {
|
||||
func (c *Cron) AddOnce(pattern string, job func(), name ...string) (*Job, error) {
|
||||
if entry, err := c.Add(pattern, job, name...); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
@ -97,7 +97,7 @@ func (c *Cron) AddOnce(pattern string, job func(), name ...string) (*Entry, erro
|
||||
// AddTimes adds a timed task which can be run specified times.
|
||||
// A unique <name> can be bound with the timed task.
|
||||
// It returns and error if the <name> is already used.
|
||||
func (c *Cron) AddTimes(pattern string, times int, job func(), name ...string) (*Entry, error) {
|
||||
func (c *Cron) AddTimes(pattern string, times int, job func(), name ...string) (*Job, error) {
|
||||
if entry, err := c.Add(pattern, job, name...); err != nil {
|
||||
return nil, err
|
||||
} else {
|
||||
@ -146,9 +146,9 @@ func (c *Cron) DelayAddTimes(delay time.Duration, pattern string, times int, job
|
||||
|
||||
// Search returns a scheduled task with the specified <name>.
|
||||
// It returns nil if no found.
|
||||
func (c *Cron) Search(name string) *Entry {
|
||||
func (c *Cron) Search(name string) *Job {
|
||||
if v := c.entries.Get(name); v != nil {
|
||||
return v.(*Entry)
|
||||
return v.(*Job)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
@ -182,7 +182,7 @@ func (c *Cron) Stop(name ...string) {
|
||||
// Remove deletes scheduled task which named <name>.
|
||||
func (c *Cron) Remove(name string) {
|
||||
if v := c.entries.Get(name); v != nil {
|
||||
v.(*Entry).Close()
|
||||
v.(*Job).Close()
|
||||
}
|
||||
}
|
||||
|
||||
@ -197,10 +197,10 @@ func (c *Cron) Size() int {
|
||||
}
|
||||
|
||||
// Entries return all timed tasks as slice(order by registered time asc).
|
||||
func (c *Cron) Entries() []*Entry {
|
||||
func (c *Cron) Entries() []*Job {
|
||||
array := garray.NewSortedArraySize(c.entries.Size(), func(v1, v2 interface{}) int {
|
||||
entry1 := v1.(*Entry)
|
||||
entry2 := v2.(*Entry)
|
||||
entry1 := v1.(*Job)
|
||||
entry2 := v2.(*Job)
|
||||
if entry1.Time.Nanosecond() > entry2.Time.Nanosecond() {
|
||||
return 1
|
||||
}
|
||||
@ -208,13 +208,13 @@ func (c *Cron) Entries() []*Entry {
|
||||
}, true)
|
||||
c.entries.RLockFunc(func(m map[string]interface{}) {
|
||||
for _, v := range m {
|
||||
array.Add(v.(*Entry))
|
||||
array.Add(v.(*Job))
|
||||
}
|
||||
})
|
||||
entries := make([]*Entry, array.Len())
|
||||
entries := make([]*Job, array.Len())
|
||||
array.RLockFunc(func(array []interface{}) {
|
||||
for k, v := range array {
|
||||
entries[k] = v.(*Entry)
|
||||
entries[k] = v.(*Job)
|
||||
}
|
||||
})
|
||||
return entries
|
||||
|
||||
@ -18,28 +18,28 @@ import (
|
||||
)
|
||||
|
||||
// Timed task entry.
|
||||
type Entry struct {
|
||||
type Job struct {
|
||||
cron *Cron // Cron object belonged to.
|
||||
entry *gtimer.Entry // Associated gtimer.Entry.
|
||||
job *gtimer.Job // Associated gtimer.Job.
|
||||
schedule *cronSchedule // Timed schedule object.
|
||||
jobName string // Callback function name(address info).
|
||||
times *gtype.Int // Running times limit.
|
||||
Name string // Entry name.
|
||||
Name string // Job name.
|
||||
Job func() `json:"-"` // Callback function.
|
||||
Time time.Time // Registered time.
|
||||
}
|
||||
|
||||
// addEntry creates and returns a new Entry object.
|
||||
// addJob creates and returns a new Job object.
|
||||
// Param <job> is the callback function for timed task execution.
|
||||
// Param <singleton> specifies whether timed task executing in singleton mode.
|
||||
// Param <name> names this entry for manual control.
|
||||
func (c *Cron) addEntry(pattern string, job func(), singleton bool, name ...string) (*Entry, error) {
|
||||
func (c *Cron) addJob(pattern string, job func(), singleton bool, name ...string) (*Job, error) {
|
||||
schedule, err := newSchedule(pattern)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
// No limit for <times>, for gtimer checking scheduling every second.
|
||||
entry := &Entry{
|
||||
entry := &Job{
|
||||
cron: c,
|
||||
schedule: schedule,
|
||||
jobName: runtime.FuncForPC(reflect.ValueOf(job).Pointer()).Name(),
|
||||
@ -57,57 +57,57 @@ func (c *Cron) addEntry(pattern string, job func(), singleton bool, name ...stri
|
||||
// It should start running after the entry is added to the entries map,
|
||||
// to avoid the task from running during adding where the entries
|
||||
// does not have the entry information, which might cause panic.
|
||||
entry.entry = gtimer.AddEntry(time.Second, entry.check, singleton, -1, gtimer.StatusStopped)
|
||||
entry.job = gtimer.AddJob(time.Second, entry.check, singleton, -1, gtimer.StatusStopped)
|
||||
c.entries.Set(entry.Name, entry)
|
||||
entry.entry.Start()
|
||||
entry.job.Start()
|
||||
return entry, nil
|
||||
}
|
||||
|
||||
// IsSingleton return whether this entry is a singleton timed task.
|
||||
func (entry *Entry) IsSingleton() bool {
|
||||
return entry.entry.IsSingleton()
|
||||
func (entry *Job) IsSingleton() bool {
|
||||
return entry.job.IsSingleton()
|
||||
}
|
||||
|
||||
// SetSingleton sets the entry running in singleton mode.
|
||||
func (entry *Entry) SetSingleton(enabled bool) {
|
||||
entry.entry.SetSingleton(true)
|
||||
func (entry *Job) SetSingleton(enabled bool) {
|
||||
entry.job.SetSingleton(true)
|
||||
}
|
||||
|
||||
// SetTimes sets the times which the entry can run.
|
||||
func (entry *Entry) SetTimes(times int) {
|
||||
func (entry *Job) SetTimes(times int) {
|
||||
entry.times.Set(times)
|
||||
}
|
||||
|
||||
// Status returns the status of entry.
|
||||
func (entry *Entry) Status() int {
|
||||
return entry.entry.Status()
|
||||
func (entry *Job) Status() int {
|
||||
return entry.job.Status()
|
||||
}
|
||||
|
||||
// SetStatus sets the status of the entry.
|
||||
func (entry *Entry) SetStatus(status int) int {
|
||||
return entry.entry.SetStatus(status)
|
||||
func (entry *Job) SetStatus(status int) int {
|
||||
return entry.job.SetStatus(status)
|
||||
}
|
||||
|
||||
// Start starts running the entry.
|
||||
func (entry *Entry) Start() {
|
||||
entry.entry.Start()
|
||||
func (entry *Job) Start() {
|
||||
entry.job.Start()
|
||||
}
|
||||
|
||||
// Stop stops running the entry.
|
||||
func (entry *Entry) Stop() {
|
||||
entry.entry.Stop()
|
||||
func (entry *Job) Stop() {
|
||||
entry.job.Stop()
|
||||
}
|
||||
|
||||
// Close stops and removes the entry from cron.
|
||||
func (entry *Entry) Close() {
|
||||
func (entry *Job) Close() {
|
||||
entry.cron.entries.Remove(entry.Name)
|
||||
entry.entry.Close()
|
||||
entry.job.Close()
|
||||
}
|
||||
|
||||
// Timed task check execution.
|
||||
// The running times limits feature is implemented by gcron.Entry and cannot be implemented by gtimer.Entry.
|
||||
// gcron.Entry relies on gtimer to implement a scheduled task check for gcron.Entry per second.
|
||||
func (entry *Entry) check() {
|
||||
// The running times limits feature is implemented by gcron.Job and cannot be implemented by gtimer.Job.
|
||||
// gcron.Job relies on gtimer to implement a scheduled task check for gcron.Job per second.
|
||||
func (entry *Job) check() {
|
||||
if entry.schedule.meet(time.Now()) {
|
||||
path := entry.cron.GetLogPath()
|
||||
level := entry.cron.GetLogLevel()
|
||||
@ -125,7 +125,7 @@ func (entry *Entry) check() {
|
||||
// Running times check.
|
||||
times := entry.times.Add(-1)
|
||||
if times <= 0 {
|
||||
if entry.entry.SetStatus(StatusClosed) == StatusClosed || times < 0 {
|
||||
if entry.job.SetStatus(StatusClosed) == StatusClosed || times < 0 {
|
||||
return
|
||||
}
|
||||
}
|
||||
@ -139,7 +139,7 @@ func (entry *Entry) check() {
|
||||
} else {
|
||||
glog.Path(path).Level(level).Debugf("[gcron] %s(%s) %s end", entry.Name, entry.schedule.pattern, entry.jobName)
|
||||
}
|
||||
if entry.entry.Status() == StatusClosed {
|
||||
if entry.job.Status() == StatusClosed {
|
||||
entry.Close()
|
||||
}
|
||||
}()
|
||||
|
||||
@ -16,7 +16,7 @@ import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func TestCron_Entry_Operations(t *testing.T) {
|
||||
func TestCron_Job_Operations(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
var (
|
||||
cron = gcron.New()
|
||||
|
||||
@ -4,8 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Package gtimer implements Hierarchical Timing Wheel for interval/delayed jobs
|
||||
// running and management.
|
||||
// Package gtimer implements timer for interval/delayed jobs running and management.
|
||||
//
|
||||
// This package is designed for management for millions of timing jobs. The differences
|
||||
// between gtimer and gcron are as follows:
|
||||
@ -21,33 +20,53 @@ package gtimer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"math"
|
||||
"sync"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/os/gcmd"
|
||||
)
|
||||
|
||||
// Timer is the timer manager, which uses ticks to calculate the timing interval.
|
||||
type Timer struct {
|
||||
mu sync.RWMutex
|
||||
queue *priorityQueue // queue is a priority queue based on heap structure.
|
||||
status *gtype.Int // status is the current timer status.
|
||||
ticks *gtype.Int64 // ticks is the proceeded interval number by the timer.
|
||||
options TimerOptions // timer options is used for timer configuration.
|
||||
}
|
||||
|
||||
// TimerOptions is the configuration object for Timer.
|
||||
type TimerOptions struct {
|
||||
Interval time.Duration // Interval is the interval escaped of the timer.
|
||||
}
|
||||
|
||||
const (
|
||||
StatusReady = 0 // Job is ready for running.
|
||||
StatusRunning = 1 // Job is already running.
|
||||
StatusStopped = 2 // Job is stopped.
|
||||
StatusReset = 3 // Job is reset.
|
||||
StatusClosed = -1 // Job is closed and waiting to be deleted.
|
||||
panicExit = "exit" // Internal usage for custom job exit function with panic.
|
||||
defaultTimes = math.MaxInt32 // Default limit running times, a big number.
|
||||
defaultSlotNumber = 10 // Default slot number.
|
||||
defaultWheelInterval = 60 // Default wheel interval, for better manually reading.
|
||||
defaultWheelLevel = 5 // Default wheel level.
|
||||
StatusReady = 0 // Job or Timer is ready for running.
|
||||
StatusRunning = 1 // Job or Timer is already running.
|
||||
StatusStopped = 2 // Job or Timer is stopped.
|
||||
StatusClosed = -1 // Job or Timer is closed and waiting to be deleted.
|
||||
panicExit = "exit" // panicExit is used for custom job exit with panic.
|
||||
defaultTimes = math.MaxInt32 // defaultTimes is the default limit running times, a big number.
|
||||
defaultTimerInterval = 100 // defaultTimerInterval is the default timer interval in milliseconds.
|
||||
cmdEnvKey = "gf.gtimer" // Configuration key for command argument or environment.
|
||||
)
|
||||
|
||||
var (
|
||||
defaultSlots = gcmd.GetOptWithEnv(fmt.Sprintf("%s.slots", cmdEnvKey), defaultSlotNumber).Int()
|
||||
defaultLevel = gcmd.GetOptWithEnv(fmt.Sprintf("%s.level", cmdEnvKey), defaultWheelLevel).Int()
|
||||
defaultInterval = gcmd.GetOptWithEnv(fmt.Sprintf("%s.interval", cmdEnvKey), defaultWheelInterval).Duration() * time.Millisecond
|
||||
defaultTimer = New(defaultSlots, defaultInterval, defaultLevel)
|
||||
defaultTimer = New()
|
||||
defaultInterval = gcmd.GetOptWithEnv(
|
||||
fmt.Sprintf("%s.interval", cmdEnvKey), defaultTimerInterval,
|
||||
).Duration() * time.Millisecond
|
||||
)
|
||||
|
||||
// DefaultOptions creates and returns a default options object for Timer creation.
|
||||
func DefaultOptions() TimerOptions {
|
||||
return TimerOptions{
|
||||
Interval: defaultInterval,
|
||||
}
|
||||
}
|
||||
|
||||
// SetTimeout runs the job once after duration of <delay>.
|
||||
// It is like the one in javascript.
|
||||
func SetTimeout(delay time.Duration, job JobFunc) {
|
||||
@ -61,11 +80,11 @@ func SetInterval(interval time.Duration, job JobFunc) {
|
||||
}
|
||||
|
||||
// Add adds a timing job to the default timer, which runs in interval of <interval>.
|
||||
func Add(interval time.Duration, job JobFunc) *Entry {
|
||||
func Add(interval time.Duration, job JobFunc) *Job {
|
||||
return defaultTimer.Add(interval, job)
|
||||
}
|
||||
|
||||
// AddEntry adds a timing job to the default timer with detailed parameters.
|
||||
// AddJob adds a timing job to the default timer with detailed parameters.
|
||||
//
|
||||
// The parameter <interval> specifies the running interval of the job.
|
||||
//
|
||||
@ -76,22 +95,22 @@ func Add(interval time.Duration, job JobFunc) *Entry {
|
||||
// exits if its run times exceeds the <times>.
|
||||
//
|
||||
// The parameter <status> specifies the job status when it's firstly added to the timer.
|
||||
func AddEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry {
|
||||
return defaultTimer.AddEntry(interval, job, singleton, times, status)
|
||||
func AddJob(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Job {
|
||||
return defaultTimer.AddJob(interval, job, singleton, times, status)
|
||||
}
|
||||
|
||||
// AddSingleton is a convenience function for add singleton mode job.
|
||||
func AddSingleton(interval time.Duration, job JobFunc) *Entry {
|
||||
func AddSingleton(interval time.Duration, job JobFunc) *Job {
|
||||
return defaultTimer.AddSingleton(interval, job)
|
||||
}
|
||||
|
||||
// AddOnce is a convenience function for adding a job which only runs once and then exits.
|
||||
func AddOnce(interval time.Duration, job JobFunc) *Entry {
|
||||
func AddOnce(interval time.Duration, job JobFunc) *Job {
|
||||
return defaultTimer.AddOnce(interval, job)
|
||||
}
|
||||
|
||||
// AddTimes is a convenience function for adding a job which is limited running times.
|
||||
func AddTimes(interval time.Duration, times int, job JobFunc) *Entry {
|
||||
func AddTimes(interval time.Duration, times int, job JobFunc) *Job {
|
||||
return defaultTimer.AddTimes(interval, times, job)
|
||||
}
|
||||
|
||||
@ -101,10 +120,10 @@ func DelayAdd(delay time.Duration, interval time.Duration, job JobFunc) {
|
||||
defaultTimer.DelayAdd(delay, interval, job)
|
||||
}
|
||||
|
||||
// DelayAddEntry adds a timing job after delay of <interval> duration.
|
||||
// Also see AddEntry.
|
||||
func DelayAddEntry(delay time.Duration, interval time.Duration, job JobFunc, singleton bool, times int, status int) {
|
||||
defaultTimer.DelayAddEntry(delay, interval, job, singleton, times, status)
|
||||
// DelayAddJob adds a timing job after delay of <interval> duration.
|
||||
// Also see AddJob.
|
||||
func DelayAddJob(delay time.Duration, interval time.Duration, job JobFunc, singleton bool, times int, status int) {
|
||||
defaultTimer.DelayAddJob(delay, interval, job, singleton, times, status)
|
||||
}
|
||||
|
||||
// DelayAddSingleton adds a timing job after delay of <interval> duration.
|
||||
|
||||
@ -1,211 +0,0 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). 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://github.com/gogf/gf.
|
||||
|
||||
package gtimer
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
)
|
||||
|
||||
// Entry is the timing job entry to wheel.
|
||||
type Entry struct {
|
||||
name string
|
||||
wheel *wheel // Belonged wheel.
|
||||
job JobFunc // The job function.
|
||||
singleton *gtype.Bool // Singleton mode.
|
||||
status *gtype.Int // Job status.
|
||||
times *gtype.Int // Limit running times.
|
||||
intervalTicks int64 // The interval ticks of the job.
|
||||
createTicks int64 // Timer ticks when the job installed.
|
||||
createMs int64 // The timestamp in milliseconds when job installed.
|
||||
intervalMs int64 // The interval milliseconds of the job.
|
||||
installIntervalMs int64 // Interval when first installation in milliseconds.
|
||||
}
|
||||
|
||||
// JobFunc is the job function.
|
||||
type JobFunc = func()
|
||||
|
||||
// addEntry adds a timing job to the wheel.
|
||||
func (w *wheel) addEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry {
|
||||
if times <= 0 {
|
||||
times = defaultTimes
|
||||
}
|
||||
var (
|
||||
intervalMs = interval.Nanoseconds() / 1e6
|
||||
intervalTicks = intervalMs / w.intervalMs
|
||||
)
|
||||
if intervalTicks == 0 {
|
||||
// If the given interval is lesser than the one of the wheel,
|
||||
// then sets it to one tick, which means it will be run in one interval.
|
||||
intervalTicks = 1
|
||||
}
|
||||
var (
|
||||
nowMs = time.Now().UnixNano() / 1e6
|
||||
nowTicks = w.ticks.Val()
|
||||
entry = &Entry{
|
||||
wheel: w,
|
||||
job: job,
|
||||
times: gtype.NewInt(times),
|
||||
status: gtype.NewInt(status),
|
||||
createTicks: nowTicks,
|
||||
intervalTicks: intervalTicks,
|
||||
singleton: gtype.NewBool(singleton),
|
||||
createMs: nowMs,
|
||||
intervalMs: intervalMs,
|
||||
installIntervalMs: intervalMs,
|
||||
}
|
||||
)
|
||||
// Install the job to the list of the slot.
|
||||
w.slots[(nowTicks+intervalTicks)%w.number].PushBack(entry)
|
||||
return entry
|
||||
}
|
||||
|
||||
// addEntryByParent adds a timing job with parent entry.
|
||||
// The parameter `rollOn` specifies if just rolling on the entry, which was not met the runnable requirement
|
||||
// and not executed previously. This is true often when the job internal is too long.
|
||||
func (w *wheel) addEntryByParent(rollOn bool, nowMs, interval int64, parent *Entry) *Entry {
|
||||
intervalTicks := interval / w.intervalMs
|
||||
if intervalTicks == 0 {
|
||||
intervalTicks = 1
|
||||
}
|
||||
nowTicks := w.ticks.Val()
|
||||
entry := &Entry{
|
||||
name: parent.name,
|
||||
wheel: w,
|
||||
job: parent.job,
|
||||
times: parent.times,
|
||||
status: parent.status,
|
||||
intervalTicks: intervalTicks,
|
||||
singleton: parent.singleton,
|
||||
createTicks: nowTicks,
|
||||
createMs: nowMs,
|
||||
intervalMs: interval,
|
||||
installIntervalMs: parent.installIntervalMs,
|
||||
}
|
||||
if rollOn {
|
||||
entry.createMs = parent.createMs
|
||||
if parent.wheel.level == w.level {
|
||||
entry.createTicks = parent.createTicks
|
||||
}
|
||||
}
|
||||
w.slots[(nowTicks+intervalTicks)%w.number].PushBack(entry)
|
||||
return entry
|
||||
}
|
||||
|
||||
// Status returns the status of the job.
|
||||
func (entry *Entry) Status() int {
|
||||
return entry.status.Val()
|
||||
}
|
||||
|
||||
// SetStatus custom sets the status for the job.
|
||||
func (entry *Entry) SetStatus(status int) int {
|
||||
return entry.status.Set(status)
|
||||
}
|
||||
|
||||
// Start starts the job.
|
||||
func (entry *Entry) Start() {
|
||||
entry.status.Set(StatusReady)
|
||||
}
|
||||
|
||||
// Stop stops the job.
|
||||
func (entry *Entry) Stop() {
|
||||
entry.status.Set(StatusStopped)
|
||||
}
|
||||
|
||||
//Reset reset the job.
|
||||
func (entry *Entry) Reset() {
|
||||
entry.status.Set(StatusReset)
|
||||
}
|
||||
|
||||
// Close closes the job, and then it will be removed from the timer.
|
||||
func (entry *Entry) Close() {
|
||||
entry.status.Set(StatusClosed)
|
||||
}
|
||||
|
||||
// IsSingleton checks and returns whether the job in singleton mode.
|
||||
func (entry *Entry) IsSingleton() bool {
|
||||
return entry.singleton.Val()
|
||||
}
|
||||
|
||||
// SetSingleton sets the job singleton mode.
|
||||
func (entry *Entry) SetSingleton(enabled bool) {
|
||||
entry.singleton.Set(enabled)
|
||||
}
|
||||
|
||||
// SetTimes sets the limit running times for the job.
|
||||
func (entry *Entry) SetTimes(times int) {
|
||||
entry.times.Set(times)
|
||||
}
|
||||
|
||||
// Run runs the job.
|
||||
func (entry *Entry) Run() {
|
||||
entry.job()
|
||||
}
|
||||
|
||||
// check checks if the job should be run in given ticks and timestamp milliseconds.
|
||||
func (entry *Entry) check(nowTicks int64, nowMs int64) (runnable, addable bool) {
|
||||
switch entry.status.Val() {
|
||||
case StatusStopped:
|
||||
return false, true
|
||||
case StatusClosed:
|
||||
return false, false
|
||||
case StatusReset:
|
||||
return false, true
|
||||
}
|
||||
// Firstly checks using the ticks, this may be low precision as one tick is a little bit long.
|
||||
//if entry.name == "1" {
|
||||
// intlog.Print("check:", nowTicks-entry.createTicks, nowTicks, entry.createTicks, entry.intervalTicks)
|
||||
//}
|
||||
if diff := nowTicks - entry.createTicks; diff > 0 && diff%entry.intervalTicks == 0 {
|
||||
// If not the lowest level wheel.
|
||||
if entry.wheel.level > 0 {
|
||||
diffMs := nowMs - entry.createMs
|
||||
switch {
|
||||
// Add it to the next slot, which means it will run on next interval.
|
||||
case diffMs < entry.wheel.timer.intervalMs:
|
||||
entry.wheel.slots[(nowTicks+entry.intervalTicks)%entry.wheel.number].PushBack(entry)
|
||||
return false, false
|
||||
|
||||
// Normal rolls on the job.
|
||||
case diffMs >= entry.wheel.timer.intervalMs:
|
||||
// Calculate the leftover milliseconds,
|
||||
// if it is greater than the minimum interval, then re-install it.
|
||||
if leftMs := entry.intervalMs - diffMs; leftMs > entry.wheel.timer.intervalMs {
|
||||
// Re-calculate and re-installs the job proper slot.
|
||||
entry.wheel.timer.doAddEntryByParent(false, nowMs, leftMs, entry)
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
}
|
||||
// Singleton mode check.
|
||||
if entry.IsSingleton() {
|
||||
// Note that it is atomic operation to ensure concurrent safety.
|
||||
if entry.status.Set(StatusRunning) == StatusRunning {
|
||||
return false, true
|
||||
}
|
||||
}
|
||||
// Limit running times.
|
||||
times := entry.times.Add(-1)
|
||||
if times <= 0 {
|
||||
// Note that it is atomic operation to ensure concurrent safety.
|
||||
if entry.status.Set(StatusClosed) == StatusClosed || times < 0 {
|
||||
return false, false
|
||||
}
|
||||
}
|
||||
// This means it does not limit the running times.
|
||||
// I know it's ugly, but it is surely high performance for running times limit.
|
||||
if times < 2000000000 && times > 1000000000 {
|
||||
entry.times.Set(defaultTimes)
|
||||
}
|
||||
//if entry.name == "1" {
|
||||
// intlog.Print("runnable:", nowTicks-entry.createTicks, nowTicks, entry.createTicks, entry.createTicks, entry.interval)
|
||||
//}
|
||||
return true, true
|
||||
}
|
||||
return false, true
|
||||
}
|
||||
134
os/gtimer/gtimer_job.go
Normal file
134
os/gtimer/gtimer_job.go
Normal file
@ -0,0 +1,134 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). 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://github.com/gogf/gf.
|
||||
|
||||
package gtimer
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"math"
|
||||
)
|
||||
|
||||
// Job is the timing job.
|
||||
type Job struct {
|
||||
job JobFunc // The job function.
|
||||
timer *Timer // Belonged timer.
|
||||
ticks int64 // The job runs every ticks.
|
||||
times *gtype.Int // Limit running times.
|
||||
status *gtype.Int // Job status.
|
||||
singleton *gtype.Bool // Singleton mode.
|
||||
nextTicks *gtype.Int64 // Next run ticks of the job.
|
||||
}
|
||||
|
||||
// JobFunc is the job function.
|
||||
type JobFunc = func()
|
||||
|
||||
// Status returns the status of the job.
|
||||
func (j *Job) Status() int {
|
||||
return j.status.Val()
|
||||
}
|
||||
|
||||
// Run runs the timer job asynchronously.
|
||||
func (j *Job) Run() {
|
||||
leftRunningTimes := j.times.Add(-1)
|
||||
if leftRunningTimes < 0 {
|
||||
j.status.Set(StatusClosed)
|
||||
return
|
||||
}
|
||||
// This means it does not limit the running times.
|
||||
// I know it's ugly, but it is surely high performance for running times limit.
|
||||
if leftRunningTimes < 2000000000 && leftRunningTimes > 1000000000 {
|
||||
j.times.Set(math.MaxInt32)
|
||||
}
|
||||
go func() {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if err != panicExit {
|
||||
panic(err)
|
||||
} else {
|
||||
j.Close()
|
||||
return
|
||||
}
|
||||
}
|
||||
if j.Status() == StatusRunning {
|
||||
j.SetStatus(StatusReady)
|
||||
}
|
||||
}()
|
||||
j.job()
|
||||
}()
|
||||
}
|
||||
|
||||
// doCheckAndRunByTicks checks the if job can run in given timer ticks,
|
||||
// it runs asynchronously if the given `currentTimerTicks` meets or else
|
||||
// it increments its ticks and waits for next running check.
|
||||
func (j *Job) doCheckAndRunByTicks(currentTimerTicks int64) {
|
||||
// Ticks check.
|
||||
if currentTimerTicks < j.nextTicks.Val() {
|
||||
return
|
||||
}
|
||||
j.nextTicks.Set(currentTimerTicks + j.ticks)
|
||||
// Perform job checking.
|
||||
switch j.status.Val() {
|
||||
case StatusRunning:
|
||||
if j.IsSingleton() {
|
||||
return
|
||||
}
|
||||
case StatusReady:
|
||||
if !j.status.Cas(StatusReady, StatusRunning) {
|
||||
return
|
||||
}
|
||||
case StatusStopped:
|
||||
return
|
||||
case StatusClosed:
|
||||
return
|
||||
}
|
||||
// Perform job running.
|
||||
j.Run()
|
||||
}
|
||||
|
||||
// SetStatus custom sets the status for the job.
|
||||
func (j *Job) SetStatus(status int) int {
|
||||
return j.status.Set(status)
|
||||
}
|
||||
|
||||
// Start starts the job.
|
||||
func (j *Job) Start() {
|
||||
j.status.Set(StatusReady)
|
||||
}
|
||||
|
||||
// Stop stops the job.
|
||||
func (j *Job) Stop() {
|
||||
j.status.Set(StatusStopped)
|
||||
}
|
||||
|
||||
// Close closes the job, and then it will be removed from the timer.
|
||||
func (j *Job) Close() {
|
||||
j.status.Set(StatusClosed)
|
||||
}
|
||||
|
||||
// Reset reset the job, which resets its ticks for next running.
|
||||
func (j *Job) Reset() {
|
||||
j.nextTicks.Set(j.timer.ticks.Val() + j.ticks)
|
||||
}
|
||||
|
||||
// IsSingleton checks and returns whether the job in singleton mode.
|
||||
func (j *Job) IsSingleton() bool {
|
||||
return j.singleton.Val()
|
||||
}
|
||||
|
||||
// SetSingleton sets the job singleton mode.
|
||||
func (j *Job) SetSingleton(enabled bool) {
|
||||
j.singleton.Set(enabled)
|
||||
}
|
||||
|
||||
// Job returns the job function of this job.
|
||||
func (j *Job) Job() JobFunc {
|
||||
return j.job
|
||||
}
|
||||
|
||||
// SetTimes sets the limit running times for the job.
|
||||
func (j *Job) SetTimes(times int) {
|
||||
j.times.Set(times)
|
||||
}
|
||||
@ -1,93 +0,0 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). 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://github.com/gogf/gf.
|
||||
|
||||
package gtimer
|
||||
|
||||
import (
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/container/glist"
|
||||
)
|
||||
|
||||
// start starts the ticker using a standalone goroutine.
|
||||
func (w *wheel) start() {
|
||||
go func() {
|
||||
var (
|
||||
tickDuration = time.Duration(w.intervalMs) * time.Millisecond
|
||||
ticker = time.NewTicker(tickDuration)
|
||||
)
|
||||
for {
|
||||
select {
|
||||
case <-ticker.C:
|
||||
switch w.timer.status.Val() {
|
||||
case StatusRunning:
|
||||
w.proceed()
|
||||
|
||||
case StatusStopped:
|
||||
// Do nothing.
|
||||
|
||||
case StatusClosed:
|
||||
ticker.Stop()
|
||||
return
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// proceed checks and rolls on the job.
|
||||
// If a timing job is time for running, it runs in an asynchronous goroutine,
|
||||
// or else it removes from current slot and re-installs the job to another wheel and slot
|
||||
// according to its leftover interval in milliseconds.
|
||||
func (w *wheel) proceed() {
|
||||
var (
|
||||
nowTicks = w.ticks.Add(1)
|
||||
list = w.slots[int(nowTicks%w.number)]
|
||||
length = list.Len()
|
||||
nowMs = w.timer.nowFunc().UnixNano() / 1e6
|
||||
)
|
||||
if length > 0 {
|
||||
go func(l *glist.List, nowTicks int64) {
|
||||
var entry *Entry
|
||||
for i := length; i > 0; i-- {
|
||||
if v := l.PopFront(); v == nil {
|
||||
break
|
||||
} else {
|
||||
entry = v.(*Entry)
|
||||
}
|
||||
// Checks whether the time for running.
|
||||
runnable, addable := entry.check(nowTicks, nowMs)
|
||||
if runnable {
|
||||
// Just run it in another goroutine.
|
||||
go func(entry *Entry) {
|
||||
defer func() {
|
||||
if err := recover(); err != nil {
|
||||
if err != panicExit {
|
||||
panic(err)
|
||||
} else {
|
||||
entry.Close()
|
||||
}
|
||||
}
|
||||
if entry.Status() == StatusRunning {
|
||||
entry.SetStatus(StatusReady)
|
||||
}
|
||||
}()
|
||||
entry.job()
|
||||
}(entry)
|
||||
}
|
||||
// Add job again, which make the job continuous running.
|
||||
if addable {
|
||||
// If StatusReset, reset to runnable state.
|
||||
if entry.Status() == StatusReset {
|
||||
entry.SetStatus(StatusReady)
|
||||
}
|
||||
entry.wheel.timer.doAddEntryByParent(!runnable, nowMs, entry.installIntervalMs, entry)
|
||||
}
|
||||
}
|
||||
}(list, nowTicks)
|
||||
}
|
||||
}
|
||||
91
os/gtimer/gtimer_queue.go
Normal file
91
os/gtimer/gtimer_queue.go
Normal file
@ -0,0 +1,91 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). 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://github.com/gogf/gf.
|
||||
|
||||
package gtimer
|
||||
|
||||
import (
|
||||
"container/heap"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"math"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// priorityQueue is an abstract data type similar to a regular queue or stack data structure in which
|
||||
// each element additionally has a "priority" associated with it. In a priority queue, an element with
|
||||
// high priority is served before an element with low priority.
|
||||
// priorityQueue is based on heap structure.
|
||||
type priorityQueue struct {
|
||||
mu sync.RWMutex
|
||||
heap *priorityQueueHeap // the underlying queue items manager using heap.
|
||||
latestPriority *gtype.Int64 // latestPriority stores the most priority value of the heap, which is used to check if necessary to call the Pop of heap by Timer.
|
||||
}
|
||||
|
||||
// priorityQueueHeap is a heap manager, of which the underlying `array` is a array implementing a heap structure.
|
||||
type priorityQueueHeap struct {
|
||||
array []priorityQueueItem
|
||||
}
|
||||
|
||||
// priorityQueueItem stores the queue item which has a `priority` attribute to sort itself in heap.
|
||||
type priorityQueueItem struct {
|
||||
value interface{}
|
||||
priority int64
|
||||
}
|
||||
|
||||
// newPriorityQueue creates and returns a priority queue.
|
||||
func newPriorityQueue() *priorityQueue {
|
||||
queue := &priorityQueue{
|
||||
heap: &priorityQueueHeap{
|
||||
array: make([]priorityQueueItem, 0),
|
||||
},
|
||||
latestPriority: gtype.NewInt64(math.MaxInt64),
|
||||
}
|
||||
heap.Init(queue.heap)
|
||||
return queue
|
||||
}
|
||||
|
||||
// Len retrieves and returns the length of the queue.
|
||||
func (q *priorityQueue) Len() int {
|
||||
q.mu.RLock()
|
||||
defer q.mu.RUnlock()
|
||||
return q.heap.Len()
|
||||
}
|
||||
|
||||
// LatestPriority retrieves and returns the minimum and the most priority value of the queue.
|
||||
func (q *priorityQueue) LatestPriority() int64 {
|
||||
return q.latestPriority.Val()
|
||||
}
|
||||
|
||||
// Push pushes a value to the queue.
|
||||
// The `priority` specifies the priority of the value.
|
||||
// The lesser the `priority` value the higher priority of the `value`.
|
||||
func (q *priorityQueue) Push(value interface{}, priority int64) {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
// Update the minimum priority using atomic operation.
|
||||
for {
|
||||
latestPriority := q.latestPriority.Val()
|
||||
if priority >= latestPriority {
|
||||
break
|
||||
}
|
||||
if q.latestPriority.Cas(latestPriority, priority) {
|
||||
break
|
||||
}
|
||||
}
|
||||
heap.Push(q.heap, priorityQueueItem{
|
||||
value: value,
|
||||
priority: priority,
|
||||
})
|
||||
}
|
||||
|
||||
// Pop retrieves, removes and returns the most high priority value from the queue.
|
||||
func (q *priorityQueue) Pop() interface{} {
|
||||
q.mu.Lock()
|
||||
defer q.mu.Unlock()
|
||||
if item := heap.Pop(q.heap); item != nil {
|
||||
return item.(priorityQueueItem).value
|
||||
}
|
||||
return nil
|
||||
}
|
||||
41
os/gtimer/gtimer_queue_heap.go
Normal file
41
os/gtimer/gtimer_queue_heap.go
Normal file
@ -0,0 +1,41 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). 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://github.com/gogf/gf.
|
||||
|
||||
package gtimer
|
||||
|
||||
// Len is used to implement the interface of sort.Interface.
|
||||
func (h *priorityQueueHeap) Len() int {
|
||||
return len(h.array)
|
||||
}
|
||||
|
||||
// Less is used to implement the interface of sort.Interface.
|
||||
func (h *priorityQueueHeap) Less(i, j int) bool {
|
||||
return h.array[i].priority < h.array[j].priority
|
||||
}
|
||||
|
||||
// Swap is used to implement the interface of sort.Interface.
|
||||
func (h *priorityQueueHeap) Swap(i, j int) {
|
||||
if len(h.array) == 0 {
|
||||
return
|
||||
}
|
||||
h.array[i], h.array[j] = h.array[j], h.array[i]
|
||||
}
|
||||
|
||||
// Push pushes an item to the heap.
|
||||
func (h *priorityQueueHeap) Push(x interface{}) {
|
||||
h.array = append(h.array, x.(priorityQueueItem))
|
||||
}
|
||||
|
||||
// Pop retrieves, removes and returns the most high priority item from the heap.
|
||||
func (h *priorityQueueHeap) Pop() interface{} {
|
||||
length := len(h.array)
|
||||
if length == 0 {
|
||||
return nil
|
||||
}
|
||||
item := h.array[length-1]
|
||||
h.array = h.array[0 : length-1]
|
||||
return item
|
||||
}
|
||||
@ -7,107 +7,31 @@
|
||||
package gtimer
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"time"
|
||||
|
||||
"github.com/gogf/gf/container/glist"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"time"
|
||||
)
|
||||
|
||||
// Timer is a Hierarchical Timing Wheel manager for timing jobs.
|
||||
type Timer struct {
|
||||
status *gtype.Int // Timer status.
|
||||
wheels []*wheel // The underlying wheels.
|
||||
length int // Max level of the wheels.
|
||||
number int // Slot Number of each wheel.
|
||||
intervalMs int64 // Interval of the slot in milliseconds.
|
||||
nowFunc func() time.Time // nowFunc returns the current time, which can be custom.
|
||||
}
|
||||
|
||||
// Wheel is a slot wrapper for timing job install and uninstall.
|
||||
type wheel struct {
|
||||
timer *Timer // Belonged timer.
|
||||
level int // The level in the timer.
|
||||
slots []*glist.List // Slot array.
|
||||
number int64 // Slot Number=len(slots).
|
||||
ticks *gtype.Int64 // Ticked count of the wheel, one tick is one of its interval passed.
|
||||
totalMs int64 // Total duration in milliseconds=number*interval.
|
||||
createMs int64 // Created timestamp in milliseconds.
|
||||
intervalMs int64 // Interval in milliseconds, which is the duration of one slot.
|
||||
}
|
||||
|
||||
// New creates and returns a Hierarchical Timing Wheel designed timer.
|
||||
// The parameter <interval> specifies the interval of the timer.
|
||||
// The optional parameter <level> specifies the wheels count of the timer,
|
||||
// which is defaultWheelLevel in default.
|
||||
func New(slot int, interval time.Duration, level ...int) *Timer {
|
||||
t := doNewWithoutAutoStart(slot, interval, level...)
|
||||
t.wheels[0].start()
|
||||
return t
|
||||
}
|
||||
|
||||
func doNewWithoutAutoStart(slot int, interval time.Duration, level ...int) *Timer {
|
||||
if slot <= 0 {
|
||||
panic(fmt.Sprintf("invalid slot number: %d", slot))
|
||||
}
|
||||
length := defaultWheelLevel
|
||||
if len(level) > 0 {
|
||||
length = level[0]
|
||||
}
|
||||
func New(options ...TimerOptions) *Timer {
|
||||
t := &Timer{
|
||||
status: gtype.NewInt(StatusRunning),
|
||||
wheels: make([]*wheel, length),
|
||||
length: length,
|
||||
number: slot,
|
||||
intervalMs: interval.Nanoseconds() / 1e6,
|
||||
nowFunc: func() time.Time {
|
||||
return time.Now()
|
||||
},
|
||||
queue: newPriorityQueue(),
|
||||
status: gtype.NewInt(StatusRunning),
|
||||
ticks: gtype.NewInt64(),
|
||||
}
|
||||
for i := 0; i < length; i++ {
|
||||
if i > 0 {
|
||||
n := time.Duration(t.wheels[i-1].totalMs) * time.Millisecond
|
||||
if n <= 0 {
|
||||
panic(fmt.Sprintf(`inteval is too large with level: %dms x %d`, interval, length))
|
||||
}
|
||||
w := t.newWheel(i, slot, n)
|
||||
t.wheels[i] = w
|
||||
t.wheels[i-1].addEntry(n, w.proceed, false, defaultTimes, StatusReady)
|
||||
if i == length-1 {
|
||||
t.wheels[i].addEntry(n, w.proceed, false, defaultTimes, StatusReady)
|
||||
}
|
||||
} else {
|
||||
w := t.newWheel(i, slot, interval)
|
||||
t.wheels[i] = w
|
||||
}
|
||||
if len(options) > 0 {
|
||||
t.options = options[0]
|
||||
} else {
|
||||
t.options = DefaultOptions()
|
||||
}
|
||||
go t.loop()
|
||||
return t
|
||||
}
|
||||
|
||||
// newWheel creates and returns a single wheel.
|
||||
func (t *Timer) newWheel(level int, slot int, interval time.Duration) *wheel {
|
||||
w := &wheel{
|
||||
timer: t,
|
||||
level: level,
|
||||
slots: make([]*glist.List, slot),
|
||||
number: int64(slot),
|
||||
ticks: gtype.NewInt64(),
|
||||
totalMs: int64(slot) * interval.Nanoseconds() / 1e6,
|
||||
createMs: time.Now().UnixNano() / 1e6,
|
||||
intervalMs: interval.Nanoseconds() / 1e6,
|
||||
}
|
||||
for i := int64(0); i < w.number; i++ {
|
||||
w.slots[i] = glist.New(true)
|
||||
}
|
||||
return w
|
||||
}
|
||||
|
||||
// Add adds a timing job to the timer, which runs in interval of <interval>.
|
||||
func (t *Timer) Add(interval time.Duration, job JobFunc) *Entry {
|
||||
return t.doAddEntry(interval, job, false, defaultTimes, StatusReady)
|
||||
func (t *Timer) Add(interval time.Duration, job JobFunc) *Job {
|
||||
return t.createJob(interval, job, false, defaultTimes, StatusReady)
|
||||
}
|
||||
|
||||
// AddEntry adds a timing job to the timer with detailed parameters.
|
||||
// AddJob adds a timing job to the timer with detailed parameters.
|
||||
//
|
||||
// The parameter <interval> specifies the running interval of the job.
|
||||
//
|
||||
@ -118,23 +42,23 @@ func (t *Timer) Add(interval time.Duration, job JobFunc) *Entry {
|
||||
// exits if its run times exceeds the <times>.
|
||||
//
|
||||
// The parameter <status> specifies the job status when it's firstly added to the timer.
|
||||
func (t *Timer) AddEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry {
|
||||
return t.doAddEntry(interval, job, singleton, times, status)
|
||||
func (t *Timer) AddJob(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Job {
|
||||
return t.createJob(interval, job, singleton, times, status)
|
||||
}
|
||||
|
||||
// AddSingleton is a convenience function for add singleton mode job.
|
||||
func (t *Timer) AddSingleton(interval time.Duration, job JobFunc) *Entry {
|
||||
return t.doAddEntry(interval, job, true, defaultTimes, StatusReady)
|
||||
func (t *Timer) AddSingleton(interval time.Duration, job JobFunc) *Job {
|
||||
return t.createJob(interval, job, true, defaultTimes, StatusReady)
|
||||
}
|
||||
|
||||
// AddOnce is a convenience function for adding a job which only runs once and then exits.
|
||||
func (t *Timer) AddOnce(interval time.Duration, job JobFunc) *Entry {
|
||||
return t.doAddEntry(interval, job, true, 1, StatusReady)
|
||||
func (t *Timer) AddOnce(interval time.Duration, job JobFunc) *Job {
|
||||
return t.createJob(interval, job, true, 1, StatusReady)
|
||||
}
|
||||
|
||||
// AddTimes is a convenience function for adding a job which is limited running times.
|
||||
func (t *Timer) AddTimes(interval time.Duration, times int, job JobFunc) *Entry {
|
||||
return t.doAddEntry(interval, job, true, times, StatusReady)
|
||||
func (t *Timer) AddTimes(interval time.Duration, times int, job JobFunc) *Job {
|
||||
return t.createJob(interval, job, true, times, StatusReady)
|
||||
}
|
||||
|
||||
// DelayAdd adds a timing job after delay of <interval> duration.
|
||||
@ -145,11 +69,11 @@ func (t *Timer) DelayAdd(delay time.Duration, interval time.Duration, job JobFun
|
||||
})
|
||||
}
|
||||
|
||||
// DelayAddEntry adds a timing job after delay of <interval> duration.
|
||||
// Also see AddEntry.
|
||||
func (t *Timer) DelayAddEntry(delay time.Duration, interval time.Duration, job JobFunc, singleton bool, times int, status int) {
|
||||
// DelayAddJob adds a timing job after delay of <interval> duration.
|
||||
// Also see AddJob.
|
||||
func (t *Timer) DelayAddJob(delay time.Duration, interval time.Duration, job JobFunc, singleton bool, times int, status int) {
|
||||
t.AddOnce(delay, func() {
|
||||
t.AddEntry(interval, job, singleton, times, status)
|
||||
t.AddJob(interval, job, singleton, times, status)
|
||||
})
|
||||
}
|
||||
|
||||
@ -192,77 +116,29 @@ func (t *Timer) Close() {
|
||||
t.status.Set(StatusClosed)
|
||||
}
|
||||
|
||||
// doAddEntry adds a timing job to timer for internal usage.
|
||||
func (t *Timer) doAddEntry(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Entry {
|
||||
return t.wheels[t.getLevelByIntervalMs(interval.Nanoseconds()/1e6)].addEntry(interval, job, singleton, times, status)
|
||||
}
|
||||
|
||||
// doAddEntryByParent adds a timing job to timer with parent entry for internal usage.
|
||||
func (t *Timer) doAddEntryByParent(rollOn bool, nowMs, interval int64, parent *Entry) *Entry {
|
||||
return t.wheels[t.getLevelByIntervalMs(interval)].addEntryByParent(rollOn, nowMs, interval, parent)
|
||||
}
|
||||
|
||||
// getLevelByIntervalMs calculates and returns the level of timer wheel with given milliseconds.
|
||||
func (t *Timer) getLevelByIntervalMs(intervalMs int64) int {
|
||||
pos, cmp := t.binSearchIndex(intervalMs)
|
||||
switch cmp {
|
||||
// If equals to the last comparison value, do not add it directly to this wheel,
|
||||
// but loop and continue comparison from the index to the first level,
|
||||
// and add it to the proper level wheel.
|
||||
case 0:
|
||||
fallthrough
|
||||
// If lesser than the last comparison value,
|
||||
// loop and continue comparison from the index to the first level,
|
||||
// and add it to the proper level wheel.
|
||||
case -1:
|
||||
i := pos
|
||||
for ; i > 0; i-- {
|
||||
if intervalMs > t.wheels[i].intervalMs && intervalMs <= t.wheels[i].totalMs {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return i
|
||||
|
||||
// If greater than the last comparison value,
|
||||
// loop and continue comparison from the index to the last level,
|
||||
// and add it to the proper level wheel.
|
||||
case 1:
|
||||
i := pos
|
||||
for ; i < t.length-1; i++ {
|
||||
if intervalMs > t.wheels[i].intervalMs && intervalMs <= t.wheels[i].totalMs {
|
||||
return i
|
||||
}
|
||||
}
|
||||
return i
|
||||
// createJob creates and adds a timing job to the timer.
|
||||
func (t *Timer) createJob(interval time.Duration, job JobFunc, singleton bool, times int, status int) *Job {
|
||||
if times <= 0 {
|
||||
times = defaultTimes
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
// binSearchIndex uses binary search algorithm for finding the possible level of the wheel
|
||||
// for the interval value.
|
||||
func (t *Timer) binSearchIndex(n int64) (index int, result int) {
|
||||
min := 0
|
||||
max := t.length - 1
|
||||
mid := 0
|
||||
cmp := -2
|
||||
for min <= max {
|
||||
mid = min + int((max-min)/2)
|
||||
switch {
|
||||
case t.wheels[mid].intervalMs == n:
|
||||
cmp = 0
|
||||
case t.wheels[mid].intervalMs > n:
|
||||
cmp = -1
|
||||
case t.wheels[mid].intervalMs < n:
|
||||
cmp = 1
|
||||
}
|
||||
switch cmp {
|
||||
case -1:
|
||||
max = mid - 1
|
||||
case 1:
|
||||
min = mid + 1
|
||||
case 0:
|
||||
return mid, cmp
|
||||
}
|
||||
var (
|
||||
intervalTicksOfJob = int64(interval / t.options.Interval)
|
||||
)
|
||||
if intervalTicksOfJob == 0 {
|
||||
// If the given interval is lesser than the one of the wheel,
|
||||
// then sets it to one tick, which means it will be run in one interval.
|
||||
intervalTicksOfJob = 1
|
||||
}
|
||||
return mid, cmp
|
||||
nextTicks := t.ticks.Val() + intervalTicksOfJob
|
||||
j := &Job{
|
||||
job: job,
|
||||
timer: t,
|
||||
ticks: intervalTicksOfJob,
|
||||
times: gtype.NewInt(times),
|
||||
status: gtype.NewInt(status),
|
||||
singleton: gtype.NewBool(singleton),
|
||||
nextTicks: gtype.NewInt64(nextTicks),
|
||||
}
|
||||
t.queue.Push(j, nextTicks)
|
||||
return j
|
||||
}
|
||||
|
||||
68
os/gtimer/gtimer_timer_loop.go
Normal file
68
os/gtimer/gtimer_timer_loop.go
Normal file
@ -0,0 +1,68 @@
|
||||
// Copyright GoFrame Author(https://goframe.org). 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://github.com/gogf/gf.
|
||||
|
||||
package gtimer
|
||||
|
||||
import "time"
|
||||
|
||||
// loop starts the ticker using a standalone goroutine.
|
||||
func (t *Timer) loop() {
|
||||
go func() {
|
||||
var (
|
||||
currentTimerTicks int64
|
||||
timerIntervalTicker = time.NewTicker(t.options.Interval)
|
||||
)
|
||||
defer timerIntervalTicker.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-timerIntervalTicker.C:
|
||||
// Check the timer status.
|
||||
switch t.status.Val() {
|
||||
case StatusRunning:
|
||||
// Timer proceeding.
|
||||
currentTimerTicks = t.ticks.Add(1)
|
||||
if currentTimerTicks >= t.queue.LatestPriority() {
|
||||
t.proceed(currentTimerTicks)
|
||||
}
|
||||
|
||||
case StatusStopped:
|
||||
// Do nothing.
|
||||
|
||||
case StatusClosed:
|
||||
// Timer exits.
|
||||
return
|
||||
}
|
||||
}
|
||||
}
|
||||
}()
|
||||
}
|
||||
|
||||
// proceed proceeds the timer job checking and running logic.
|
||||
func (t *Timer) proceed(currentTimerTicks int64) {
|
||||
var (
|
||||
value interface{}
|
||||
)
|
||||
for {
|
||||
value = t.queue.Pop()
|
||||
if value == nil {
|
||||
break
|
||||
}
|
||||
job := value.(*Job)
|
||||
// It checks if it meets the ticks requirement.
|
||||
if jobNextTicks := job.nextTicks.Val(); currentTimerTicks < jobNextTicks {
|
||||
// It push the job back if current ticks does not meet its running ticks requirement.
|
||||
t.queue.Push(job, job.nextTicks.Val())
|
||||
break
|
||||
}
|
||||
// It checks the job running requirements and then does asynchronous running.
|
||||
job.doCheckAndRunByTicks(currentTimerTicks)
|
||||
// Status check: push back or ignore it.
|
||||
if job.Status() != StatusClosed {
|
||||
// It pushes the job back to queue for next running.
|
||||
t.queue.Push(job, job.nextTicks.Val())
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -14,7 +14,7 @@ import (
|
||||
)
|
||||
|
||||
var (
|
||||
timer = gtimer.New(5, 30*time.Millisecond)
|
||||
timer = gtimer.New()
|
||||
)
|
||||
|
||||
func Benchmark_Add(b *testing.B) {
|
||||
|
||||
@ -39,10 +39,10 @@ func TestSetInterval(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestAddEntry(t *testing.T) {
|
||||
func TestAddJob(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.New(true)
|
||||
gtimer.AddEntry(200*time.Millisecond, func() {
|
||||
gtimer.AddJob(200*time.Millisecond, func() {
|
||||
array.Append(1)
|
||||
}, false, 2, gtimer.StatusReady)
|
||||
time.Sleep(1100 * time.Millisecond)
|
||||
@ -86,10 +86,10 @@ func TestDelayAdd(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestDelayAddEntry(t *testing.T) {
|
||||
func TestDelayAddJob(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.New(true)
|
||||
gtimer.DelayAddEntry(200*time.Millisecond, 200*time.Millisecond, func() {
|
||||
gtimer.DelayAddJob(200*time.Millisecond, 200*time.Millisecond, func() {
|
||||
array.Append(1)
|
||||
}, false, 2, gtimer.StatusReady)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
|
||||
@ -4,7 +4,7 @@
|
||||
// If a copy of the MIT was not distributed with this file,
|
||||
// You can obtain one at https://github.com/gogf/gf.
|
||||
|
||||
// Entry Operations
|
||||
// Job Operations
|
||||
|
||||
package gtimer_test
|
||||
|
||||
@ -17,40 +17,40 @@ import (
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
)
|
||||
|
||||
func TestEntry_Start_Stop_Close(t *testing.T) {
|
||||
func TestJob_Start_Stop_Close(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timer := New()
|
||||
array := garray.New(true)
|
||||
entry := timer.Add(200*time.Millisecond, func() {
|
||||
job := timer.Add(200*time.Millisecond, func() {
|
||||
array.Append(1)
|
||||
})
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
t.Assert(array.Len(), 1)
|
||||
entry.Stop()
|
||||
job.Stop()
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
t.Assert(array.Len(), 1)
|
||||
entry.Start()
|
||||
job.Start()
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
t.Assert(array.Len(), 2)
|
||||
entry.Close()
|
||||
job.Close()
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
t.Assert(array.Len(), 2)
|
||||
|
||||
t.Assert(entry.Status(), gtimer.StatusClosed)
|
||||
t.Assert(job.Status(), gtimer.StatusClosed)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEntry_Singleton(t *testing.T) {
|
||||
func TestJob_Singleton(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timer := New()
|
||||
array := garray.New(true)
|
||||
entry := timer.Add(200*time.Millisecond, func() {
|
||||
job := timer.Add(200*time.Millisecond, func() {
|
||||
array.Append(1)
|
||||
time.Sleep(10 * time.Second)
|
||||
})
|
||||
t.Assert(entry.IsSingleton(), false)
|
||||
entry.SetSingleton(true)
|
||||
t.Assert(entry.IsSingleton(), true)
|
||||
t.Assert(job.IsSingleton(), false)
|
||||
job.SetSingleton(true)
|
||||
t.Assert(job.IsSingleton(), true)
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
t.Assert(array.Len(), 1)
|
||||
|
||||
@ -59,27 +59,28 @@ func TestEntry_Singleton(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestEntry_SetTimes(t *testing.T) {
|
||||
func TestJob_SetTimes(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timer := New()
|
||||
array := garray.New(true)
|
||||
entry := timer.Add(200*time.Millisecond, func() {
|
||||
job := timer.Add(200*time.Millisecond, func() {
|
||||
array.Append(1)
|
||||
})
|
||||
entry.SetTimes(2)
|
||||
job.SetTimes(2)
|
||||
//job.IsSingleton()
|
||||
time.Sleep(1200 * time.Millisecond)
|
||||
t.Assert(array.Len(), 2)
|
||||
})
|
||||
}
|
||||
|
||||
func TestEntry_Run(t *testing.T) {
|
||||
func TestJob_Run(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timer := New()
|
||||
array := garray.New(true)
|
||||
entry := timer.Add(1000*time.Millisecond, func() {
|
||||
job := timer.Add(1000*time.Millisecond, func() {
|
||||
array.Append(1)
|
||||
})
|
||||
entry.Run()
|
||||
job.Job()()
|
||||
t.Assert(array.Len(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -8,7 +8,6 @@ package gtimer
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/container/garray"
|
||||
"github.com/gogf/gf/container/gtype"
|
||||
"github.com/gogf/gf/test/gtest"
|
||||
"testing"
|
||||
"time"
|
||||
@ -16,35 +15,34 @@ import (
|
||||
|
||||
func TestTimer_Proceed(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
index := gtype.NewInt()
|
||||
array := garray.New(true)
|
||||
timer := doNewWithoutAutoStart(10, 60*time.Millisecond, 6)
|
||||
timer.nowFunc = func() time.Time {
|
||||
return time.Now().Add(time.Duration(index.Add(1)) * time.Millisecond * 60)
|
||||
}
|
||||
timer.AddOnce(2*time.Second, func() {
|
||||
timer := New(TimerOptions{
|
||||
Interval: time.Hour,
|
||||
})
|
||||
timer.Add(10000*time.Hour, func() {
|
||||
array.Append(1)
|
||||
})
|
||||
timer.AddOnce(1*time.Minute, func() {
|
||||
array.Append(2)
|
||||
timer.proceed(10001)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
t.Assert(array.Len(), 1)
|
||||
timer.proceed(20001)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
t.Assert(array.Len(), 2)
|
||||
})
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
array := garray.New(true)
|
||||
timer := New(TimerOptions{
|
||||
Interval: time.Millisecond * 100,
|
||||
})
|
||||
timer.AddOnce(5*time.Minute, func() {
|
||||
array.Append(3)
|
||||
timer.Add(10000*time.Hour, func() {
|
||||
array.Append(1)
|
||||
})
|
||||
timer.AddOnce(1*time.Hour, func() {
|
||||
array.Append(4)
|
||||
})
|
||||
timer.AddOnce(100*time.Minute, func() {
|
||||
array.Append(5)
|
||||
})
|
||||
timer.AddOnce(2*time.Hour, func() {
|
||||
array.Append(6)
|
||||
})
|
||||
for i := 0; i < 500000; i++ {
|
||||
timer.wheels[0].proceed()
|
||||
time.Sleep(10 * time.Microsecond)
|
||||
}
|
||||
time.Sleep(time.Second)
|
||||
t.Assert(array.Slice(), []int{1, 2, 3, 4, 5, 6})
|
||||
ticks := int64((10000 * time.Hour) / (time.Millisecond * 100))
|
||||
timer.proceed(ticks + 1)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
t.Assert(array.Len(), 1)
|
||||
timer.proceed(2*ticks + 1)
|
||||
time.Sleep(10 * time.Millisecond)
|
||||
t.Assert(array.Len(), 2)
|
||||
})
|
||||
}
|
||||
|
||||
@ -9,7 +9,6 @@
|
||||
package gtimer_test
|
||||
|
||||
import (
|
||||
"github.com/gogf/gf/os/glog"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
@ -19,7 +18,7 @@ import (
|
||||
)
|
||||
|
||||
func New() *gtimer.Timer {
|
||||
return gtimer.New(10, 10*time.Millisecond)
|
||||
return gtimer.New()
|
||||
}
|
||||
|
||||
func TestTimer_Add_Close(t *testing.T) {
|
||||
@ -28,15 +27,15 @@ func TestTimer_Add_Close(t *testing.T) {
|
||||
array := garray.New(true)
|
||||
//fmt.Println("start", time.Now())
|
||||
timer.Add(200*time.Millisecond, func() {
|
||||
//fmt.Println("entry1", time.Now())
|
||||
//fmt.Println("job1", time.Now())
|
||||
array.Append(1)
|
||||
})
|
||||
timer.Add(200*time.Millisecond, func() {
|
||||
//fmt.Println("entry2", time.Now())
|
||||
//fmt.Println("job2", time.Now())
|
||||
array.Append(1)
|
||||
})
|
||||
timer.Add(400*time.Millisecond, func() {
|
||||
//fmt.Println("entry3", time.Now())
|
||||
//fmt.Println("job3", time.Now())
|
||||
array.Append(1)
|
||||
})
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
@ -74,21 +73,21 @@ func TestTimer_Start_Stop_Close(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimer_Reset(t *testing.T) {
|
||||
func TestJob_Reset(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timer := New()
|
||||
array := garray.New(true)
|
||||
glog.Printf("start time:%d", time.Now().Unix())
|
||||
singleton := timer.AddSingleton(2*time.Second, func() {
|
||||
timestamp := time.Now().Unix()
|
||||
glog.Println(timestamp)
|
||||
array.Append(timestamp)
|
||||
job := timer.AddSingleton(500*time.Millisecond, func() {
|
||||
array.Append(1)
|
||||
})
|
||||
time.Sleep(5 * time.Second)
|
||||
glog.Printf("reset time:%d", time.Now().Unix())
|
||||
singleton.Reset()
|
||||
time.Sleep(10 * time.Second)
|
||||
t.Assert(array.Len(), 6)
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
job.Reset()
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
job.Reset()
|
||||
time.Sleep(300 * time.Millisecond)
|
||||
job.Reset()
|
||||
time.Sleep(600 * time.Millisecond)
|
||||
t.Assert(array.Len(), 1)
|
||||
})
|
||||
}
|
||||
|
||||
@ -156,11 +155,11 @@ func TestTimer_DelayAdd(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimer_DelayAddEntry(t *testing.T) {
|
||||
func TestTimer_DelayAddJob(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timer := New()
|
||||
array := garray.New(true)
|
||||
timer.DelayAddEntry(200*time.Millisecond, 200*time.Millisecond, func() {
|
||||
timer.DelayAddJob(200*time.Millisecond, 200*time.Millisecond, func() {
|
||||
array.Append(1)
|
||||
}, false, 100, gtimer.StatusReady)
|
||||
time.Sleep(250 * time.Millisecond)
|
||||
@ -227,7 +226,9 @@ func TestTimer_DelayAddTimes(t *testing.T) {
|
||||
|
||||
func TestTimer_AddLessThanInterval(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timer := gtimer.New(10, 100*time.Millisecond)
|
||||
timer := gtimer.New(gtimer.TimerOptions{
|
||||
Interval: 100 * time.Millisecond,
|
||||
})
|
||||
array := garray.New(true)
|
||||
timer.Add(20*time.Millisecond, func() {
|
||||
array.Append(1)
|
||||
@ -243,7 +244,7 @@ func TestTimer_AddLessThanInterval(t *testing.T) {
|
||||
})
|
||||
}
|
||||
|
||||
func TestTimer_AddLeveledEntry1(t *testing.T) {
|
||||
func TestTimer_AddLeveledJob1(t *testing.T) {
|
||||
gtest.C(t, func(t *gtest.T) {
|
||||
timer := New()
|
||||
array := garray.New(true)
|
||||
|
||||
Reference in New Issue
Block a user