blob: 04f914cd0a1f29756f996feb684236dd22fb0920 [file] [log] [blame]
package state
import (
"time"
"veyron/services/store/memstore/refs"
"veyron2/security"
"veyron2/storage"
)
type State struct {
snapshot *MutableSnapshot
// timestamp is the time of the last mutation applied, in nanoseconds since
// the epoch. See comment for snapshot.Mutations.Timestamp.
timestamp uint64
}
// refUpdate represents a reference change to a value.
type refUpdate struct {
id storage.ID
before refs.Set
after refs.Set
}
// New returns an empty State.
func New(admin security.PublicID) *State {
return &State{snapshot: newMutableSnapshot(admin)}
}
// Timestamp returns the timestamp of the latest mutation to the state in
// nanoseconds.
func (st *State) Timestamp() uint64 {
return st.timestamp
}
// DeepCopy creates a copy of the state. Mutations to the copy do not affect
// the original, and vice versa.
func (st *State) DeepCopy() *State {
return &State{st.MutableSnapshot(), st.timestamp}
}
// GC performs a manual garbage collection.
func (st *State) GC() {
st.snapshot.gc()
}
// Snapshot returns a read-only copy of the state.
func (st *State) Snapshot() Snapshot {
return st.snapshot.GetSnapshot()
}
// MutableSnapshot creates a copy of the state. Mutations to the copy do not
// affect the original, and vice versa.
func (st *State) MutableSnapshot() *MutableSnapshot {
return st.snapshot.deepCopy()
}
// Deletions returns the set of IDs for values that have been deleted from
// the state. Returns nil iff there have been no deletions.
func (st *State) Deletions() *Mutations {
if len(st.snapshot.deletions) == 0 {
return nil
}
// Package the deletions into a transaction.
var mu Mutations
ts := st.timestamp + 1
mu.Timestamp = ts
mu.Deletions = st.snapshot.deletions
st.timestamp = ts
st.snapshot.deletions = make(map[storage.ID]storage.Version)
return &mu
}
// ApplyMutations applies a set of mutations atomically.
//
// We don't need to check permissions because:
// 1. Permissions were checked as the mutations were created.
// 2. Preconditions ensure that all paths to modified values haven't changed.
// 3. The client cannot fabricate a mutations value.
func (st *State) ApplyMutations(mu *Mutations) error {
// Assign a timestamp.
ts := uint64(time.Now().UnixNano())
if ts <= st.timestamp {
ts = st.timestamp + 1
}
mu.Timestamp = ts
if err := st.snapshot.applyMutations(mu); err != nil {
return err
}
st.timestamp = ts
return nil
}
func (sn *MutableSnapshot) applyMutations(mu *Mutations) error {
// Check the preconditions.
table := sn.idTable
for id, pre := range mu.Preconditions {
c, ok := table.Get(&Cell{ID: id})
// If the precondition is 0, it means that the cell is being created,
// and it must not already exist. We get a precondition failure if pre
// is 0 and the cell already exists, or pre is not 0 and the cell does
// not already exist or have the expected version.
if pre == 0 && ok || pre != 0 && (!ok || c.(*Cell).Version != pre) {
return errPreconditionFailed
}
}
for id, pre := range mu.Deletions {
c, ok := table.Get(&Cell{ID: id})
// The target is not required to exist.
if ok && c.(*Cell).Version != pre {
return errPreconditionFailed
}
}
// Changes to the state begin now. These changes should not fail,
// as we don't support rollback.
// Apply the mutations.
updates := make([]*refUpdate, 0, len(mu.Delta))
for id, m := range mu.Delta {
d := refs.BuildDir(m.Dir)
cl, ok := table.Get(&Cell{ID: id})
sn.aclCache.Invalidate(id)
if !ok {
c := &Cell{
ID: id,
Version: m.Postcondition,
Value: m.Value,
Dir: d,
Tags: m.Tags,
refs: m.refs,
inRefs: refs.Empty,
}
table = table.Put(c)
updates = append(updates, &refUpdate{id: c.ID, before: refs.Empty, after: c.refs})
} else {
c := cl.(*Cell)
cp := *c
cp.Version = m.Postcondition
cp.Value = m.Value
cp.Dir = d
cp.Tags = m.Tags
cp.refs = m.refs
table = table.Put(&cp)
updates = append(updates, &refUpdate{id: c.ID, before: c.refs, after: cp.refs})
}
}
sn.idTable = table
// Add the refs.
for _, u := range updates {
sn.updateRefs(u.id, u.before, u.after)
}
// Redirect the rootID.
if mu.SetRootID && mu.RootID != sn.rootID {
if mu.RootID != nullID {
sn.ref(mu.RootID)
}
if sn.rootID != nullID {
sn.unref(sn.rootID)
}
sn.rootID = mu.RootID
}
return nil
}