blob: 001c258783793c9107c53d8221e61d608969fbf0 [file] [log] [blame]
// Copyright 2015 The Vanadium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.
package vclock
// This file defines the VClock struct and methods.
import (
"sync"
"time"
"v.io/v23/verror"
"v.io/x/lib/vlog"
"v.io/x/ref/services/syncbase/common"
"v.io/x/ref/services/syncbase/store"
)
// VClock holds everything needed to provide UTC time estimates.
// VClock is thread-safe.
type VClock struct {
// Syncbase top-level store (i.e. not a database-specific store).
st store.Store
// Protects sysClock. We use RWMutex because writes can never happen in
// production: they only happen on calls to InjectFakeSysClock, which can only
// be called in development mode.
mu sync.RWMutex
sysClock SystemClock
}
// NewVClock creates a new VClock.
func NewVClock(st store.Store) *VClock {
return &VClock{
st: st,
sysClock: newRealSystemClock(),
}
}
////////////////////////////////////////////////////////////////////////////////
// Methods for determining the time
// TODO(sadovsky): Cache VClockData in memory, protected by a lock to ensure
// cache consistency. That way, "Now" won't need to return an error and
// "ApplySkew" can stop taking VClockData as input.
// Now returns our estimate of current UTC time based on SystemClock time and
// our estimate of its skew relative to NTP time.
func (c *VClock) Now() (time.Time, error) {
data := &VClockData{}
if err := c.GetVClockData(data); err != nil {
vlog.Errorf("vclock: error fetching VClockData: %v", err)
return time.Time{}, err
}
c.mu.RLock()
defer c.mu.RUnlock()
return c.ApplySkew(c.sysClock.Now(), data), nil
}
// ApplySkew applies skew correction to the given system clock time.
func (c *VClock) ApplySkew(sysTime time.Time, data *VClockData) time.Time {
return sysTime.Add(time.Duration(data.Skew))
}
////////////////////////////////////////////////////////////////////////////////
// Methods for initializing, reading, and updating VClockData in the store
const vclockDataKey = common.VClockPrefix
// GetVClockData fills 'data' with VClockData read from the store.
func (c *VClock) GetVClockData(data *VClockData) error {
return store.Get(nil, c.st, vclockDataKey, data)
}
// InitVClockData initializes VClockData in the store (if needed).
func (c *VClock) InitVClockData() error {
return store.RunInTransaction(c.st, func(tx store.Transaction) error {
if err := store.Get(nil, tx, vclockDataKey, &VClockData{}); err == nil {
return nil
} else if verror.ErrorID(err) != verror.ErrNoExist.ID {
return err
}
// No existing VClockData; write fresh VClockData.
now, elapsedTime, err := c.SysClockVals()
if err != nil {
return err
}
return store.Put(nil, tx, vclockDataKey, &VClockData{
SystemTimeAtBoot: now.Add(-elapsedTime),
ElapsedTimeSinceBoot: elapsedTime,
})
})
}
// UpdateVClockData reads VClockData from the store, applies the given function
// to produce new VClockData, and writes the resulting VClockData back to the
// store. The entire read-modify-write operation is performed inside of a
// transaction.
func (c *VClock) UpdateVClockData(fn func(*VClockData) (*VClockData, error)) error {
return store.RunInTransactionWithOpts(c.st, &store.TransactionOptions{NumAttempts: 1}, func(tx store.Transaction) error {
data := &VClockData{}
if err := store.Get(nil, tx, vclockDataKey, data); err != nil {
return err
}
if newVClockData, err := fn(data); err != nil {
return err
} else {
return store.Put(nil, tx, vclockDataKey, newVClockData)
}
})
}
////////////////////////////////////////////////////////////////////////////////
// Methods for accessing system clock time
// SysClockVals returns the system clock's Now and ElapsedTime values.
func (c *VClock) SysClockVals() (time.Time, time.Duration, error) {
c.mu.RLock()
defer c.mu.RUnlock()
if elapsedTime, err := c.sysClock.ElapsedTime(); err != nil {
return time.Time{}, 0, err
} else {
return c.sysClock.Now(), elapsedTime, nil
}
}
// SysClockValsIfNotChanged returns the system clock's Now and ElapsedTime
// values, but returns an error if it detects that the system clock has changed
// based on the given origNow and origElapsedTime.
// IMPORTANT: origNow and origElapsedTime must have come from a call to
// SysClockVals, not SysClockValsIfNotChanged, to avoid a race condition in
// system clock change detection.
func (c *VClock) SysClockValsIfNotChanged(origNow time.Time, origElapsedTime time.Duration) (time.Time, time.Duration, error) {
c.mu.RLock()
defer c.mu.RUnlock()
now := c.sysClock.Now()
if elapsedTime, err := c.sysClock.ElapsedTime(); err != nil {
return time.Time{}, 0, err
} else if hasSysClockChanged(origNow, now, origElapsedTime, elapsedTime) {
return time.Time{}, 0, err
} else {
return now, elapsedTime, nil
}
}
////////////////////////////////////////////////////////////////////////////////
// Development mode methods
func (c *VClock) InjectFakeSysClock(now time.Time, elapsedTime time.Duration) {
c.mu.Lock()
defer c.mu.Unlock()
c.sysClock = newFakeSystemClock(now, elapsedTime)
}