blob: bb82a347add56cf3ae269ce96b92834f3d5dc880 [file] [log] [blame]
package memstore
import (
"sync"
"veyron/services/store/memstore/state"
"veyron/services/store/service"
"veyron2/verror"
)
// Transaction is the type of transactions. Each transaction has a snapshot of
// the entire state of the store; <store> is the shared *Store, assigned when
// the transaction was first used, and <snapshot> is MutableSnapshot that
// includes any changes in the transaction.
//
// Transactions are initially empty. The <store> and <snapshot> fields are
// set when the transaction is first used.
type Transaction struct {
mutex sync.Mutex
store *Store
snapshot *state.MutableSnapshot
}
var (
errBadTransaction = verror.BadArgf("bad transaction")
)
// newNilTransaction is used when nil is passed in as the transaction for an
// object operation. This means that the operation is to be performed on the
// state <st>.
func (st *Store) newNilTransaction() *Transaction {
st.Lock()
defer st.Unlock()
return &Transaction{store: st, snapshot: st.State.MutableSnapshot()}
}
// getTransaction returns the *Transaction value for the service.Transaction.
// Returns bool commit==true iff the transaction argument is nil, which means
// that the transaction lifetime is the duration of the operation (so the
// transaction should be committed immediately after the operation that uses it
// is performed).
func (st *Store) getTransaction(tr service.Transaction) (*Transaction, bool, error) {
if tr == nil {
return st.newNilTransaction(), true, nil
}
t, ok := tr.(*Transaction)
if !ok {
return nil, false, errBadTransaction
}
t.useState(st)
return t, false, nil
}
// GetTransactionSnapshot returns a read-only snapshot from the transaction.
func (st *Store) GetTransactionSnapshot(tr service.Transaction) (state.Snapshot, error) {
t, _, err := st.getTransaction(tr)
if err != nil {
return nil, err
}
return t.Snapshot(), nil
}
// NewTransaction returns a fresh transaction containing no changes.
func NewTransaction() *Transaction {
return &Transaction{}
}
// useState sets the state in the transaction if it hasn't been set already.
func (t *Transaction) useState(st *Store) {
t.mutex.Lock()
defer t.mutex.Unlock()
if t.store == nil {
t.store = st
t.snapshot = st.State.MutableSnapshot()
}
}
// Commit commits the transaction. The commit may abort if there have been
// concurrent changes to the store that conflict with the changes in the
// transaction. If so, Commit returns an error and leaves the store unchanged.
// The Commit is atomic -- all of the changes in the transaction are applied to
// the state, or none of them are.
func (t *Transaction) Commit() error {
t.mutex.Lock()
st, sn := t.store, t.snapshot
t.mutex.Unlock()
if st == nil || sn == nil {
return nil
}
err := st.ApplyMutations(sn.Mutations())
// Log deleted objects via garbage collection. This occurs within the
// transaction boundary, i.e. before the state lock is released.
// TODO(tilaks): separate discovery and collection, collect lazily.
st.maybeGC(err)
return err
}
// maybeGC will run a garbage collection if the committed transaction may result
// in an orphaned object.
// For now, the heuristic is simple - if the transaction succeeded, run GC().
func (st *Store) maybeGC(err error) {
if err == nil {
st.GC()
}
}
// Abort discards a transaction. This is an optimization; transactions
// eventually time out and get discarded. However, live transactions
// consume resources, so it is good practice to clean up.
func (t *Transaction) Abort() error {
t.mutex.Lock()
t.store = nil
t.snapshot = nil
t.mutex.Unlock()
return nil
}
// Snapshot returns a read-only snapshot.
func (t *Transaction) Snapshot() state.Snapshot {
t.mutex.Lock()
defer t.mutex.Unlock()
return t.snapshot.GetSnapshot()
}