pkg/timecache: implement a time cache
This commit is contained in:
parent
8300621799
commit
55b57549a6
2 changed files with 275 additions and 0 deletions
127
pkg/timecache/timecache.go
Normal file
127
pkg/timecache/timecache.go
Normal file
|
@ -0,0 +1,127 @@
|
|||
// Package timecache provides a cache for the system clock, to avoid calls to
|
||||
// time.Now().
|
||||
// The time is stored as one int64 which holds the number of nanoseconds since
|
||||
// the Unix Epoch. The value is accessed using atomic primitives, without
|
||||
// locking.
|
||||
// The package runs a global singleton TimeCache that is is updated every
|
||||
// second.
|
||||
package timecache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"sync/atomic"
|
||||
"time"
|
||||
)
|
||||
|
||||
// t is the global TimeCache.
|
||||
var t *TimeCache
|
||||
|
||||
func init() {
|
||||
t = &TimeCache{
|
||||
clock: time.Now().UnixNano(),
|
||||
closed: make(chan struct{}),
|
||||
running: make(chan struct{}),
|
||||
}
|
||||
|
||||
go t.Run(1 * time.Second)
|
||||
}
|
||||
|
||||
// A TimeCache is a cache for the current system time.
|
||||
// The cached time has nanosecond precision.
|
||||
type TimeCache struct {
|
||||
// clock saves the current time's nanoseconds since the Epoch.
|
||||
// Must be accessed atomically.
|
||||
clock int64
|
||||
|
||||
closed chan struct{}
|
||||
running chan struct{}
|
||||
m sync.Mutex
|
||||
}
|
||||
|
||||
// New returns a new TimeCache instance.
|
||||
// The TimeCache must be started to update the time.
|
||||
func New() *TimeCache {
|
||||
return &TimeCache{
|
||||
clock: time.Now().UnixNano(),
|
||||
closed: make(chan struct{}),
|
||||
running: make(chan struct{}),
|
||||
}
|
||||
}
|
||||
|
||||
// Run runs the TimeCache, updating the cached clock value once every interval
|
||||
// and blocks until Stop is called.
|
||||
func (t *TimeCache) Run(interval time.Duration) {
|
||||
t.m.Lock()
|
||||
select {
|
||||
case <-t.running:
|
||||
panic("Run called multiple times")
|
||||
default:
|
||||
}
|
||||
close(t.running)
|
||||
t.m.Unlock()
|
||||
|
||||
tick := time.NewTicker(interval)
|
||||
defer tick.Stop()
|
||||
for {
|
||||
select {
|
||||
case <-t.closed:
|
||||
tick.Stop()
|
||||
return
|
||||
case now := <-tick.C:
|
||||
atomic.StoreInt64(&t.clock, now.UnixNano())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Stop stops the TimeCache.
|
||||
// The cached time remains valid but will not be updated anymore.
|
||||
// A TimeCache can not be restarted. Construct a new one instead.
|
||||
// Calling Stop again is a no-op.
|
||||
func (t *TimeCache) Stop() {
|
||||
t.m.Lock()
|
||||
defer t.m.Unlock()
|
||||
|
||||
select {
|
||||
case <-t.closed:
|
||||
return
|
||||
default:
|
||||
}
|
||||
close(t.closed)
|
||||
}
|
||||
|
||||
// Now returns the cached time as a time.Time value.
|
||||
func (t *TimeCache) Now() time.Time {
|
||||
return time.Unix(0, atomic.LoadInt64(&t.clock))
|
||||
}
|
||||
|
||||
// NowUnixNano returns the cached time as nanoseconds since the Unix Epoch.
|
||||
func (t *TimeCache) NowUnixNano() int64 {
|
||||
return atomic.LoadInt64(&t.clock)
|
||||
}
|
||||
|
||||
// NowUnix returns the cached time as seconds since the Unix Epoch.
|
||||
func (t *TimeCache) NowUnix() int64 {
|
||||
// Adopted from time.Unix
|
||||
nsec := atomic.LoadInt64(&t.clock)
|
||||
sec := nsec / 1e9
|
||||
nsec -= sec * 1e9
|
||||
if nsec < 0 {
|
||||
sec--
|
||||
}
|
||||
return sec
|
||||
}
|
||||
|
||||
// Now calls Now on the global TimeCache instance.
|
||||
func Now() time.Time {
|
||||
return t.Now()
|
||||
}
|
||||
|
||||
// NowUnixNano calls NowUnixNano on the global TimeCache instance.
|
||||
func NowUnixNano() int64 {
|
||||
return t.NowUnixNano()
|
||||
}
|
||||
|
||||
// NowUnix calls NowUnix on the global TimeCache instance.
|
||||
func NowUnix() int64 {
|
||||
return t.NowUnix()
|
||||
}
|
148
pkg/timecache/timecache_test.go
Normal file
148
pkg/timecache/timecache_test.go
Normal file
|
@ -0,0 +1,148 @@
|
|||
package timecache
|
||||
|
||||
import (
|
||||
"sync"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestNew(t *testing.T) {
|
||||
c := New()
|
||||
require.NotNil(t, c)
|
||||
|
||||
now := c.Now()
|
||||
require.False(t, now.IsZero())
|
||||
|
||||
nsec := c.NowUnixNano()
|
||||
require.NotEqual(t, 0, nsec)
|
||||
|
||||
sec := c.NowUnix()
|
||||
require.NotEqual(t, 0, sec)
|
||||
}
|
||||
|
||||
func TestRunStop(t *testing.T) {
|
||||
c := New()
|
||||
require.NotNil(t, c)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
c.Run(1 * time.Second)
|
||||
}()
|
||||
|
||||
c.Stop()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func TestMultipleStop(t *testing.T) {
|
||||
c := New()
|
||||
require.NotNil(t, c)
|
||||
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
c.Run(1 * time.Second)
|
||||
}()
|
||||
|
||||
c.Stop()
|
||||
c.Stop()
|
||||
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func doBenchmark(b *testing.B, f func(tc *TimeCache) func(*testing.PB)) {
|
||||
tc := New()
|
||||
require.NotNil(b, tc)
|
||||
wg := &sync.WaitGroup{}
|
||||
wg.Add(1)
|
||||
go func() {
|
||||
defer wg.Done()
|
||||
tc.Run(1 * time.Second)
|
||||
}()
|
||||
|
||||
b.RunParallel(f(tc))
|
||||
|
||||
tc.Stop()
|
||||
wg.Wait()
|
||||
}
|
||||
|
||||
func BenchmarkNow(b *testing.B) {
|
||||
doBenchmark(b, func(tc *TimeCache) func(pb *testing.PB) {
|
||||
return func(pb *testing.PB) {
|
||||
var now time.Time
|
||||
for pb.Next() {
|
||||
now = tc.Now()
|
||||
}
|
||||
_ = now
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkNowUnix(b *testing.B) {
|
||||
doBenchmark(b, func(tc *TimeCache) func(pb *testing.PB) {
|
||||
return func(pb *testing.PB) {
|
||||
var now int64
|
||||
for pb.Next() {
|
||||
now = tc.NowUnix()
|
||||
}
|
||||
_ = now
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkNowUnixNano(b *testing.B) {
|
||||
doBenchmark(b, func(tc *TimeCache) func(pb *testing.PB) {
|
||||
return func(pb *testing.PB) {
|
||||
var now int64
|
||||
for pb.Next() {
|
||||
now = tc.NowUnixNano()
|
||||
}
|
||||
_ = now
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkNowGlobal(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var now time.Time
|
||||
for pb.Next() {
|
||||
now = Now()
|
||||
}
|
||||
_ = now
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkNowUnixGlobal(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var now int64
|
||||
for pb.Next() {
|
||||
now = NowUnix()
|
||||
}
|
||||
_ = now
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkNowUnixNanoGlobal(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var now int64
|
||||
for pb.Next() {
|
||||
now = NowUnixNano()
|
||||
}
|
||||
_ = now
|
||||
})
|
||||
}
|
||||
|
||||
func BenchmarkTimeNow(b *testing.B) {
|
||||
b.RunParallel(func(pb *testing.PB) {
|
||||
var now time.Time
|
||||
for pb.Next() {
|
||||
now = time.Now()
|
||||
}
|
||||
_ = now
|
||||
})
|
||||
}
|
Loading…
Reference in a new issue