2021-04-24 14:36:34 -04:00
|
|
|
package sleepct
|
2021-04-18 00:30:27 -04:00
|
|
|
|
|
|
|
import (
|
2021-09-13 00:50:23 -04:00
|
|
|
"context"
|
2021-04-18 00:30:27 -04:00
|
|
|
"fmt"
|
|
|
|
"time"
|
|
|
|
)
|
|
|
|
|
2021-08-09 23:07:27 -04:00
|
|
|
// SleepCT - Sleep constant time step crates a sleep based ticker.
|
2021-09-13 00:50:23 -04:00
|
|
|
// designed to maintain a consistent sleep/tick interval.
|
2021-09-11 13:39:37 -04:00
|
|
|
// The sleeper can be paused waiting to be signaled from another go routine.
|
2021-08-09 23:07:27 -04:00
|
|
|
// This allows for the pausing of loops that do not have work to complete
|
2021-04-18 00:30:27 -04:00
|
|
|
type SleepCT struct {
|
2021-08-09 23:07:27 -04:00
|
|
|
d time.Duration // desired duration between targets
|
|
|
|
t time.Time // last time target
|
|
|
|
resume chan bool
|
2021-09-13 00:50:23 -04:00
|
|
|
wake time.Time // last wake time
|
|
|
|
drift int64 // last wake drift microseconds
|
2021-04-18 00:30:27 -04:00
|
|
|
}
|
|
|
|
|
2021-04-24 14:36:34 -04:00
|
|
|
func (s *SleepCT) Start(d time.Duration) {
|
2021-09-13 00:50:23 -04:00
|
|
|
s.resume = make(chan bool, 2)
|
2021-04-24 14:36:34 -04:00
|
|
|
if s.t.IsZero() {
|
|
|
|
s.d = d
|
|
|
|
s.t = time.Now()
|
|
|
|
} else {
|
|
|
|
panic("SleepCT already started")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2021-08-09 23:07:27 -04:00
|
|
|
// Sleep to the next target duration.
|
|
|
|
// If pause it set to true will sleep the duration and wait to be notified.
|
|
|
|
// The notification channel will be cleared when the thread wakes.
|
|
|
|
// SleepNextTarget should not be call more than once concurrently.
|
2021-09-13 00:50:23 -04:00
|
|
|
func (s *SleepCT) SleepNextTarget(ctx context.Context, pause bool) int64 {
|
2021-04-18 00:30:27 -04:00
|
|
|
|
|
|
|
now := time.Now()
|
|
|
|
|
2021-09-13 00:50:23 -04:00
|
|
|
// if target is zero safety net
|
2021-04-18 00:30:27 -04:00
|
|
|
if s.t.IsZero() {
|
|
|
|
fmt.Println("SleepCT reset")
|
2021-09-13 00:50:23 -04:00
|
|
|
s.t = now.Add(-s.d)
|
2021-04-18 00:30:27 -04:00
|
|
|
}
|
|
|
|
|
2021-08-09 23:07:27 -04:00
|
|
|
// Sleep to Next Target
|
2021-09-13 00:50:23 -04:00
|
|
|
s.t = s.t.Add(s.d)
|
2021-04-18 00:30:27 -04:00
|
|
|
|
2021-09-13 00:50:23 -04:00
|
|
|
// Compute the desired sleep time to reach the target
|
2021-08-09 23:07:27 -04:00
|
|
|
d := time.Until(s.t)
|
2021-04-18 00:30:27 -04:00
|
|
|
|
2021-09-13 00:50:23 -04:00
|
|
|
// Sleep
|
2021-04-18 00:30:27 -04:00
|
|
|
time.Sleep(d)
|
|
|
|
|
2021-09-13 00:50:23 -04:00
|
|
|
// record the wake time
|
|
|
|
s.wake = time.Now()
|
|
|
|
s.drift = s.wake.Sub(s.t).Microseconds()
|
|
|
|
|
|
|
|
// fmt.Println(s.t.UnixMilli(), d.Milliseconds(), wake.UnixMilli(), drift, pause, len(s.resume))
|
|
|
|
|
|
|
|
// external pause control
|
2021-08-09 23:07:27 -04:00
|
|
|
if pause {
|
2021-09-13 00:50:23 -04:00
|
|
|
// don't pause if the notification channel has something
|
2021-08-09 23:07:27 -04:00
|
|
|
if len(s.resume) == 0 {
|
2021-09-13 00:50:23 -04:00
|
|
|
// fmt.Println("pause")
|
|
|
|
select {
|
|
|
|
case <-s.resume:
|
|
|
|
case <-ctx.Done():
|
|
|
|
// fmt.Println("sleepct ctx exit")
|
|
|
|
}
|
2021-08-09 23:07:27 -04:00
|
|
|
// if we did pause set the last sleep target to now
|
2021-09-13 00:50:23 -04:00
|
|
|
s.t = time.Now()
|
2021-08-09 23:07:27 -04:00
|
|
|
}
|
|
|
|
}
|
2021-04-18 00:30:27 -04:00
|
|
|
|
2021-08-09 23:07:27 -04:00
|
|
|
// Drain the resume channel
|
|
|
|
select {
|
|
|
|
case <-s.resume:
|
|
|
|
default:
|
|
|
|
}
|
2021-08-23 00:00:39 -04:00
|
|
|
|
2021-09-13 00:50:23 -04:00
|
|
|
// return the drift for monitoring purposes
|
|
|
|
return s.drift
|
2021-04-18 00:30:27 -04:00
|
|
|
}
|
2021-08-09 23:07:27 -04:00
|
|
|
|
|
|
|
// Notify attempts to resume a paused sleeper.
|
|
|
|
// It is safe to call notify from other processes and as often as desired.
|
|
|
|
func (s *SleepCT) Notify() {
|
2021-09-13 00:50:23 -04:00
|
|
|
select {
|
|
|
|
case s.resume <- true:
|
|
|
|
default:
|
|
|
|
}
|
2021-04-18 00:30:27 -04:00
|
|
|
}
|