diff --git a/container/gqueue/gqueue.go b/container/gqueue/gqueue.go index dc2f9d39d..b8a2dfbaa 100644 --- a/container/gqueue/gqueue.go +++ b/container/gqueue/gqueue.go @@ -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() diff --git a/container/gqueue/gqueue_bench_test.go b/container/gqueue/gqueue_z_bench_test.go similarity index 100% rename from container/gqueue/gqueue_bench_test.go rename to container/gqueue/gqueue_z_bench_test.go diff --git a/container/gqueue/gqueue_unit_test.go b/container/gqueue/gqueue_z_unit_test.go similarity index 100% rename from container/gqueue/gqueue_unit_test.go rename to container/gqueue/gqueue_z_unit_test.go diff --git a/net/ghttp/ghttp_server.go b/net/ghttp/ghttp_server.go index 7b347ce6d..5896ace8c 100644 --- a/net/ghttp/ghttp_server.go +++ b/net/ghttp/ghttp_server.go @@ -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) { diff --git a/os/gcron/gcron.go b/os/gcron/gcron.go index 7f45c4b2c..b3a17f426 100644 --- a/os/gcron/gcron.go +++ b/os/gcron/gcron.go @@ -50,7 +50,7 @@ func GetLogLevel() int { // Add adds a timed task to default cron object. // A unique can be bound with the timed task. // It returns and error if the 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 can be bound with the timed task. // It returns and error if the 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 can be bound with the timed task. // It returns and error if the 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 can be bound with the timed task. // It returns and error if the 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 . // 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() } diff --git a/os/gcron/gcron_cron.go b/os/gcron/gcron_cron.go index 28e2b8522..de9493fcc 100644 --- a/os/gcron/gcron_cron.go +++ b/os/gcron/gcron_cron.go @@ -60,20 +60,20 @@ func (c *Cron) GetLogLevel() int { // Add adds a timed task. // A unique can be bound with the timed task. // It returns and error if the 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 can be bound with the timed task. // It returns and error if the 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 can be bound with the timed task. // It returns and error if the 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 can be bound with the timed task. // It returns and error if the 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 . // 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 . 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 diff --git a/os/gcron/gcron_entry.go b/os/gcron/gcron_entry.go index 94ff02f43..e0c33e474 100644 --- a/os/gcron/gcron_entry.go +++ b/os/gcron/gcron_entry.go @@ -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 is the callback function for timed task execution. // Param specifies whether timed task executing in singleton mode. // Param 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 , 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() } }() diff --git a/os/gcron/gcron_unit_2_test.go b/os/gcron/gcron_unit_2_test.go index 5956dc8bd..6fca7c849 100644 --- a/os/gcron/gcron_unit_2_test.go +++ b/os/gcron/gcron_unit_2_test.go @@ -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() diff --git a/os/gtimer/gtimer.go b/os/gtimer/gtimer.go index 80840682c..21474a498 100644 --- a/os/gtimer/gtimer.go +++ b/os/gtimer/gtimer.go @@ -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 . // 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 . -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 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 . // // The parameter 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 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 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 duration. diff --git a/os/gtimer/gtimer_entry.go b/os/gtimer/gtimer_entry.go deleted file mode 100644 index f413646c4..000000000 --- a/os/gtimer/gtimer_entry.go +++ /dev/null @@ -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 -} diff --git a/os/gtimer/gtimer_job.go b/os/gtimer/gtimer_job.go new file mode 100644 index 000000000..39fc938ae --- /dev/null +++ b/os/gtimer/gtimer_job.go @@ -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) +} diff --git a/os/gtimer/gtimer_loop.go b/os/gtimer/gtimer_loop.go deleted file mode 100644 index 725f6de46..000000000 --- a/os/gtimer/gtimer_loop.go +++ /dev/null @@ -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) - } -} diff --git a/os/gtimer/gtimer_queue.go b/os/gtimer/gtimer_queue.go new file mode 100644 index 000000000..b805d7d22 --- /dev/null +++ b/os/gtimer/gtimer_queue.go @@ -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 +} diff --git a/os/gtimer/gtimer_queue_heap.go b/os/gtimer/gtimer_queue_heap.go new file mode 100644 index 000000000..30879f770 --- /dev/null +++ b/os/gtimer/gtimer_queue_heap.go @@ -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 +} diff --git a/os/gtimer/gtimer_timer.go b/os/gtimer/gtimer_timer.go index 0280f0680..00e700d4d 100644 --- a/os/gtimer/gtimer_timer.go +++ b/os/gtimer/gtimer_timer.go @@ -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 specifies the interval of the timer. -// The optional parameter 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 . -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 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 . // // The parameter 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 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 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 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 } diff --git a/os/gtimer/gtimer_timer_loop.go b/os/gtimer/gtimer_timer_loop.go new file mode 100644 index 000000000..d2e9909d7 --- /dev/null +++ b/os/gtimer/gtimer_timer_loop.go @@ -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()) + } + } +} diff --git a/os/gtimer/gtimer_z_bench_test.go b/os/gtimer/gtimer_z_bench_test.go index 1f5eea33c..66ab4e0d6 100644 --- a/os/gtimer/gtimer_z_bench_test.go +++ b/os/gtimer/gtimer_z_bench_test.go @@ -14,7 +14,7 @@ import ( ) var ( - timer = gtimer.New(5, 30*time.Millisecond) + timer = gtimer.New() ) func Benchmark_Add(b *testing.B) { diff --git a/os/gtimer/gtimer_z_unit_api_test.go b/os/gtimer/gtimer_z_unit_api_test.go index debc25d9c..60fdb0837 100644 --- a/os/gtimer/gtimer_z_unit_api_test.go +++ b/os/gtimer/gtimer_z_unit_api_test.go @@ -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) diff --git a/os/gtimer/gtimer_z_unit_entry_test.go b/os/gtimer/gtimer_z_unit_entry_test.go index ba0d2d421..a542111b7 100644 --- a/os/gtimer/gtimer_z_unit_entry_test.go +++ b/os/gtimer/gtimer_z_unit_entry_test.go @@ -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) }) } diff --git a/os/gtimer/gtimer_z_unit_timer_internal_test.go b/os/gtimer/gtimer_z_unit_timer_internal_test.go index 5d144fb9a..9882d21c3 100644 --- a/os/gtimer/gtimer_z_unit_timer_internal_test.go +++ b/os/gtimer/gtimer_z_unit_timer_internal_test.go @@ -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) }) } diff --git a/os/gtimer/gtimer_z_unit_timer_test.go b/os/gtimer/gtimer_z_unit_timer_test.go index d6da5a8b4..f126d79cb 100644 --- a/os/gtimer/gtimer_z_unit_timer_test.go +++ b/os/gtimer/gtimer_z_unit_timer_test.go @@ -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)