state: Update docstrings for changeTrackerDB and txn

And un-embed memdb.DB to prevent accidental access to underlying
methods.
This commit is contained in:
Daniel Nephin 2020-06-02 16:05:08 -04:00
parent f9a6386c4a
commit 50db8f409a
2 changed files with 46 additions and 39 deletions

View File

@ -4,41 +4,45 @@ import (
"github.com/hashicorp/go-memdb" "github.com/hashicorp/go-memdb"
) )
// memDBWrapper is a thin shim over memdb.DB which forces all new tranactions to // memDBWrapper is a thin wrapper around memdb.DB which enables TrackChanges on
// report changes and for those changes to automatically deliver the changed // all write transactions. When the transaction is committed the changes are
// objects to our central event handler in case new streaming events need to be // sent to the eventPublisher which will create and emit change events.
// omitted.
type memDBWrapper struct { type memDBWrapper struct {
*memdb.MemDB db *memdb.MemDB
// TODO: add publisher // TODO: add publisher
} }
// Txn intercepts MemDB.Txn(). It allows read-only transactions to pass through // Txn exists to maintain backwards compatibility with memdb.DB.Txn. Preexisting
// but fails write transactions as we now need to force callers to use a // code may use it to create a read-only transaction, but it will panic if called
// slightly different API so that change capture can happen automatically. The // with write=true.
// returned Txn object is wrapped with a no-op wrapper that just keeps all //
// transactions in our state store the same type. The wrapper only has // Deprecated: use either ReadTxn, or WriteTxn.
// non-passthrough behavior for write transactions though.
func (db *memDBWrapper) Txn(write bool) *txnWrapper { func (db *memDBWrapper) Txn(write bool) *txnWrapper {
if write { if write {
panic("don't use db.Txn(true), use db.WriteTxn(idx uin64)") panic("don't use db.Txn(true), use db.WriteTxn(idx uin64)")
} }
return &txnWrapper{ return db.ReadTxn()
Txn: db.MemDB.Txn(false), }
}
// ReadTxn returns a read-only transaction which behaves exactly the same as
// memdb.Txn
func (db *memDBWrapper) ReadTxn() *txnWrapper {
return &txnWrapper{Txn: db.db.Txn(false)}
} }
// WriteTxn returns a wrapped memdb.Txn suitable for writes to the state store. // WriteTxn returns a wrapped memdb.Txn suitable for writes to the state store.
// It will track changes and publish events for them automatically when Commit // It will track changes and publish events for the changes when Commit
// is called. The idx argument should be the index of the currently operating // is called.
// Raft operation. Almost all mutations to state should happen as part of a raft //
// apply so the index of that log being applied should be plumbed through to // The idx argument must be the index of the current Raft operation. Almost
// here. A few exceptional cases are transactions that are only executed on a // all mutations to state should happen as part of a raft apply so the index of
// fresh memdb store during a Restore and a few places in tests where we insert // the log being applied can be passed to WriteTxn.
// The exceptional cases are transactions that are executed on an empty
// memdb.DB as part of Restore, and those executed by tests where we insert
// data directly into the DB. These cases may use WriteTxnRestore. // data directly into the DB. These cases may use WriteTxnRestore.
func (db *memDBWrapper) WriteTxn(idx uint64) *txnWrapper { func (db *memDBWrapper) WriteTxn(idx uint64) *txnWrapper {
t := &txnWrapper{ t := &txnWrapper{
Txn: db.MemDB.Txn(true), Txn: db.db.Txn(true),
Index: idx, Index: idx,
} }
t.Txn.TrackChanges() t.Txn.TrackChanges()
@ -47,40 +51,43 @@ func (db *memDBWrapper) WriteTxn(idx uint64) *txnWrapper {
// WriteTxnRestore returns a wrapped RW transaction that does NOT have change // WriteTxnRestore returns a wrapped RW transaction that does NOT have change
// tracking enabled. This should only be used in Restore where we need to // tracking enabled. This should only be used in Restore where we need to
// replace the entire contents of the Store without a need to track the changes // replace the entire contents of the Store without a need to track the changes.
// made and emit events. Subscribers will all reset on a restore and start // WriteTxnRestore uses a zero index since the whole restore doesn't really occur
// again. It also uses a zero index since the whole restore doesn't really occur
// at one index - the effect is to write many values that were previously // at one index - the effect is to write many values that were previously
// written across many indexes. // written across many indexes.
func (db *memDBWrapper) WriteTxnRestore() *txnWrapper { func (db *memDBWrapper) WriteTxnRestore() *txnWrapper {
t := &txnWrapper{ t := &txnWrapper{
Txn: db.MemDB.Txn(true), Txn: db.db.Txn(true),
Index: 0, Index: 0,
} }
return t return t
} }
// txnWrapper wraps a memdb.Txn to automatically capture changes and process // txnWrapper wraps a memdb.Txn to capture changes and send them to the
// events for the write as it commits. This can't be done just with txn.Defer // EventPublisher.
// because errors the callback is invoked after commit completes so errors //
// during event publishing would cause silent dropped events while the state // This can not be done with txn.Defer because the callback passed to Defer is
// store still changed and the write looked successful from the outside. // invoked after commit completes, and because the callback can not return an
// error. Any errors from the callback would be lost, which would result in a
// missing change event, even though the state store had changed.
type txnWrapper struct { type txnWrapper struct {
// Index stores the index the write is occuring at in raft if this is a write // Index in raft where the write is occurring. The value is zero for a
// transaction. If it's zero it means this is a read transaction. // read-only transaction, and for a WriteTxnRestore transaction.
// Index is stored so that it may be passed along to any subscribers as part
// of a change event.
Index uint64 Index uint64
*memdb.Txn *memdb.Txn
store *Store
} }
// Commit overrides Commit on the underlying memdb.Txn and causes events to be // Commit first pushes changes to EventPublisher, then calls Commit on the
// published for any changes. Note that it has a different signature to // underlying transaction.
// memdb.Txn - returning an error that should be checked since an error implies
// that something prevented the commit from completing.
// //
// Note that this function, unlike memdb.Txn, returns an error which must be checked
// by the caller. A non-nil error indicates that a commit failed and was not
// applied.
// TODO: currently none of the callers check error, should they all be changed? // TODO: currently none of the callers check error, should they all be changed?
func (tx *txnWrapper) Commit() error { func (tx *txnWrapper) Commit() error {
// changes may be empty if this is a read-only or WriteTxnRestore transaction.
//changes := tx.Txn.Changes() //changes := tx.Txn.Changes()
// TODO: publish changes // TODO: publish changes

View File

@ -161,7 +161,7 @@ func NewStateStore(gc *TombstoneGC) (*Store, error) {
lockDelay: NewDelay(), lockDelay: NewDelay(),
} }
s.db = &memDBWrapper{ s.db = &memDBWrapper{
MemDB: db, db: db,
} }
return s, nil return s, nil
} }