Make DataStore an interface

This commit is contained in:
Rafael Weinstein
2015-09-22 17:30:14 -07:00
parent aa474edbf3
commit adabda61a4
8 changed files with 180 additions and 158 deletions

View File

@@ -33,33 +33,3 @@ type ChunkSink interface {
Put(c Chunk)
io.Closer
}
// NewFlags creates a new instance of Flags, which declares a number of ChunkStore-related command-line flags using the golang flag package. Call this before flag.Parse().
func NewFlags() Flags {
return NewFlagsWithPrefix("")
}
// NewFlagsWithPrefix creates a new instance of Flags with the names of all flags declared therein prefixed by the given string.
func NewFlagsWithPrefix(prefix string) Flags {
return Flags{
levelDBFlags(prefix),
memoryFlags(prefix),
nopFlags(prefix),
}
}
// Flags abstracts away definitions for and handling of command-line flags for all ChunkStore implementations.
type Flags struct {
ldb ldbStoreFlags
memory memoryStoreFlags
nop nopStoreFlags
}
// CreateStore creates a ChunkStore implementation based on the values of command-line flags.
func (f Flags) CreateStore() (cs ChunkStore) {
if cs = f.ldb.createStore(); cs != nil {
} else if cs = f.memory.createStore(); cs != nil {
} else if cs = f.nop.createStore(); cs != nil {
}
return cs
}

View File

@@ -96,19 +96,19 @@ func (l *LevelDBStore) Close() error {
return nil
}
type ldbStoreFlags struct {
type LevelDBStoreFlags struct {
dir *string
maxFileHandles *int
}
func levelDBFlags(prefix string) ldbStoreFlags {
return ldbStoreFlags{
func LevelDBFlags(prefix string) LevelDBStoreFlags {
return LevelDBStoreFlags{
flag.String(prefix+"ldb", "", "directory to use for a LevelDB-backed chunkstore"),
flag.Int(prefix+"ldb-max-file-handles", 24, "max number of open file handles"),
}
}
func (f ldbStoreFlags) createStore() ChunkStore {
func (f LevelDBStoreFlags) CreateStore() ChunkStore {
if *f.dir == "" {
return nil
} else {

View File

@@ -62,17 +62,17 @@ func (l *MemoryStore) Close() error {
return nil
}
type memoryStoreFlags struct {
type MemoryStoreFlags struct {
use *bool
}
func memoryFlags(prefix string) memoryStoreFlags {
return memoryStoreFlags{
func MemoryFlags(prefix string) MemoryStoreFlags {
return MemoryStoreFlags{
flag.Bool(prefix+"mem", false, "use a memory-based (ephemeral, and private to this application) chunkstore"),
}
}
func (f memoryStoreFlags) createStore() ChunkStore {
func (f MemoryStoreFlags) CreateStore() ChunkStore {
if *f.use {
return NewMemoryStore()
} else {

View File

@@ -30,17 +30,17 @@ func (ms *NopStore) Close() error {
return nil
}
type nopStoreFlags struct {
type NopStoreFlags struct {
use *bool
}
func nopFlags(prefix string) nopStoreFlags {
return nopStoreFlags{
func NopFlags(prefix string) NopStoreFlags {
return NopStoreFlags{
flag.Bool(prefix+"nop", false, "use a /dev/null-esque chunkstore"),
}
}
func (f nopStoreFlags) createStore() ChunkStore {
func (f NopStoreFlags) CreateStore() ChunkStore {
if *f.use {
return &NopStore{}
} else {

View File

@@ -6,9 +6,9 @@ import (
"os/signal"
"syscall"
"github.com/attic-labs/noms/chunks"
"github.com/attic-labs/noms/clients/util"
"github.com/attic-labs/noms/d"
"github.com/attic-labs/noms/datas"
"github.com/attic-labs/noms/http"
)
@@ -17,15 +17,15 @@ var (
)
func main() {
flags := chunks.NewFlags()
flags := datas.NewFlags()
flag.Parse()
cs := flags.CreateStore()
if cs == nil {
ds, ok := flags.CreateDataStore()
if !ok {
flag.Usage()
return
}
server := http.NewHttpServer(cs, *port)
server := http.NewHttpServer(ds, *port)
// Shutdown server gracefully so that profile may be written
c := make(chan os.Signal, 1)
@@ -34,7 +34,7 @@ func main() {
go func() {
<-c
server.Stop()
cs.Close()
ds.Close()
}()
d.Try(func() {

View File

@@ -2,118 +2,34 @@ package datas
import (
"github.com/attic-labs/noms/chunks"
"github.com/attic-labs/noms/d"
"github.com/attic-labs/noms/http"
"github.com/attic-labs/noms/ref"
"github.com/attic-labs/noms/types"
)
// DataStore provides versioned storage for noms values. Each DataStore instance represents one moment in history. Heads() returns the Commit from each active fork at that moment. The Commit() method returns a new DataStore, representing a new moment in history.
type DataStore struct {
type DataStore interface {
chunks.ChunkStore
head *Commit
// MaybeHead returns the current Head Commit of this Datastore, which contains the current root of the DataStore's value tree, if available. If not, it returns a new Commit and 'false'.
MaybeHead() (Commit, bool)
// Head returns the current head Commit, which contains the current root of the DataStore's value tree.
Head() Commit
// Commit updates the commit that a datastore points at. The new Commit is constructed using v and the current Head. If the update cannot be performed, e.g., because of a conflict, Commit returns 'false'. The newest snapshot of the datastore is always returned.
Commit(v types.Value) (DataStore, bool)
// CommitWithParents updates the commit that a datastore points at. The new Commit is constructed using v and p. If the update cannot be performed, e.g., because of a conflict, CommitWithParents returns 'false'. The newest snapshot of the datastore is always returned.
CommitWithParents(v types.Value, p SetOfCommit) (DataStore, bool)
}
func NewDataStore(cs chunks.ChunkStore) DataStore {
return newDataStoreInternal(cs)
}
func newDataStoreInternal(cs chunks.ChunkStore) DataStore {
if (cs.Root() == ref.Ref{}) {
return DataStore{cs, nil}
}
return DataStore{cs, commitFromRef(cs.Root(), cs)}
}
func commitFromRef(commitRef ref.Ref, cs chunks.ChunkSource) *Commit {
c := CommitFromVal(types.ReadValue(commitRef, cs))
return &c
}
// MaybeHead returns the current Head Commit of this Datastore, which contains the current root of the DataStore's value tree, if available. If not, it returns a new Commit and 'false'.
func (ds *DataStore) MaybeHead() (Commit, bool) {
if ds.head == nil {
return NewCommit(), false
}
return *ds.head, true
}
// Head returns the current head Commit, which contains the current root of the DataStore's value tree.
func (ds *DataStore) Head() Commit {
c, ok := ds.MaybeHead()
d.Chk.True(ok, "DataStore has no Head.")
return c
}
// Commit updates the commit that a datastore points at. The new Commit is constructed using v and the current Head.
// If the update cannot be performed, e.g., because of a conflict, Commit returns 'false' and the current snapshot of the datastore so that the client can merge the changes and try again.
func (ds *DataStore) Commit(v types.Value) (DataStore, bool) {
p := NewSetOfCommit()
if head, ok := ds.MaybeHead(); ok {
p = p.Insert(head)
}
return ds.CommitWithParents(v, p)
}
// CommitWithParents updates the commit that a datastore points at. The new Commit is constructed using v and p.
// If the update cannot be performed, e.g., because of a conflict, CommitWithParents returns 'false' and the current snapshot of the datastore so that the client can merge the changes and try again.
func (ds *DataStore) CommitWithParents(v types.Value, p SetOfCommit) (DataStore, bool) {
ok := ds.doCommit(NewCommit().SetParents(p.NomsValue()).SetValue(v))
return newDataStoreInternal(ds.ChunkStore), ok
}
// doCommit manages concurrent access the single logical piece of mutable state: the current head. doCommit is optimistic in that it is attempting to update head making the assumption that currentRootRef is the ref of the current head. The call to UpdateRoot below will fail if that assumption fails (e.g. because of a race with another writer) and the entire algorithm must be tried again.
func (ds *DataStore) doCommit(commit Commit) bool {
currentRootRef := ds.Root()
// Note: |currentHead| may be different from ds.head and *must* be consistent with currentRootRef.
// If ds.head is nil, then any commit is allowed.
if ds.head != nil {
var currentHead Commit
if currentRootRef == ds.head.Ref() {
currentHead = *ds.head
} else {
currentHead = *commitFromRef(currentRootRef, ds)
}
// Allow only fast-forward commits.
if commit.Equals(currentHead) {
return true
} else if !descendsFrom(commit, currentHead) {
return false
}
}
// TODO: This Commit will be orphaned if this UpdateRoot below fails
newRootRef := types.WriteValue(commit.NomsValue(), ds)
ok := ds.UpdateRoot(newRootRef, currentRootRef)
return ok
}
func descendsFrom(commit, currentHead Commit) bool {
// BFS because the common case is that the ancestor is only a step or two away
ancestors := NewSetOfCommit().Insert(commit)
for !ancestors.Has(currentHead) {
if ancestors.Empty() {
return false
}
ancestors = getAncestors(ancestors)
}
return true
}
func getAncestors(commits SetOfCommit) SetOfCommit {
ancestors := NewSetOfCommit()
commits.Iter(func(c Commit) (stop bool) {
ancestors =
ancestors.Union(SetOfCommitFromVal(c.Parents()))
return
})
return ancestors
return newLocalDataStore(cs)
}
type Flags struct {
cflags chunks.Flags
ldb chunks.LevelDBStoreFlags
memory chunks.MemoryStoreFlags
nop chunks.NopStoreFlags
hflags http.Flags
}
@@ -123,20 +39,24 @@ func NewFlags() Flags {
func NewFlagsWithPrefix(prefix string) Flags {
return Flags{
chunks.NewFlagsWithPrefix(prefix),
chunks.LevelDBFlags(prefix),
chunks.MemoryFlags(prefix),
chunks.NopFlags(prefix),
http.NewFlagsWithPrefix(prefix),
}
}
func (f Flags) CreateDataStore() (DataStore, bool) {
cs := f.cflags.CreateStore()
if cs == nil {
cs = f.hflags.CreateClient()
if cs == nil {
return DataStore{}, false
}
var cs chunks.ChunkStore
if cs = f.ldb.CreateStore(); cs != nil {
} else if cs = f.memory.CreateStore(); cs != nil {
} else if cs = f.nop.CreateStore(); cs != nil {
} else if cs = f.hflags.CreateClient(); cs != nil {
}
ds := NewDataStore(cs)
return ds, true
if cs == nil {
return &LocalDataStore{}, false
}
return newLocalDataStore(cs), true
}

94
datas/datastore_common.go Normal file
View File

@@ -0,0 +1,94 @@
package datas
import (
"github.com/attic-labs/noms/chunks"
"github.com/attic-labs/noms/d"
"github.com/attic-labs/noms/ref"
"github.com/attic-labs/noms/types"
)
// DataStore provides versioned storage for noms values. Each DataStore instance represents one moment in history. Heads() returns the Commit from each active fork at that moment. The Commit() method returns a new DataStore, representing a new moment in history.
type DataStoreCommon struct {
chunks.ChunkStore
head *Commit
}
func commitFromRef(commitRef ref.Ref, cs chunks.ChunkSource) *Commit {
c := CommitFromVal(types.ReadValue(commitRef, cs))
return &c
}
func (ds *DataStoreCommon) MaybeHead() (Commit, bool) {
if ds.head == nil {
return NewCommit(), false
}
return *ds.head, true
}
func (ds *DataStoreCommon) Head() Commit {
c, ok := ds.MaybeHead()
d.Chk.True(ok, "DataStore has no Head.")
return c
}
func (ds *DataStoreCommon) commit(v types.Value) bool {
p := NewSetOfCommit()
if head, ok := ds.MaybeHead(); ok {
p = p.Insert(head)
}
return ds.commitWithParents(v, p)
}
func (ds *DataStoreCommon) commitWithParents(v types.Value, p SetOfCommit) bool {
return ds.doCommit(NewCommit().SetParents(p.NomsValue()).SetValue(v))
}
// doCommit manages concurrent access the single logical piece of mutable state: the current head. doCommit is optimistic in that it is attempting to update head making the assumption that currentRootRef is the ref of the current head. The call to UpdateRoot below will fail if that assumption fails (e.g. because of a race with another writer) and the entire algorithm must be tried again.
func (ds *DataStoreCommon) doCommit(commit Commit) bool {
currentRootRef := ds.Root()
// Note: |currentHead| may be different from ds.head and *must* be consistent with currentRootRef.
// If ds.head is nil, then any commit is allowed.
if ds.head != nil {
var currentHead Commit
if currentRootRef == ds.head.Ref() {
currentHead = *ds.head
} else {
currentHead = *commitFromRef(currentRootRef, ds)
}
// Allow only fast-forward commits.
if commit.Equals(currentHead) {
return true
} else if !descendsFrom(commit, currentHead) {
return false
}
}
// TODO: This Commit will be orphaned if this UpdateRoot below fails
newRootRef := types.WriteValue(commit.NomsValue(), ds)
ok := ds.UpdateRoot(newRootRef, currentRootRef)
return ok
}
func descendsFrom(commit, currentHead Commit) bool {
// BFS because the common case is that the ancestor is only a step or two away
ancestors := NewSetOfCommit().Insert(commit)
for !ancestors.Has(currentHead) {
if ancestors.Empty() {
return false
}
ancestors = getAncestors(ancestors)
}
return true
}
func getAncestors(commits SetOfCommit) SetOfCommit {
ancestors := NewSetOfCommit()
commits.Iter(func(c Commit) (stop bool) {
ancestors =
ancestors.Union(SetOfCommitFromVal(c.Parents()))
return
})
return ancestors
}

38
datas/local_datastore.go Normal file
View File

@@ -0,0 +1,38 @@
package datas
import (
"github.com/attic-labs/noms/chunks"
"github.com/attic-labs/noms/ref"
"github.com/attic-labs/noms/types"
)
// DataStore provides versioned storage for noms values. Each DataStore instance represents one moment in history. Heads() returns the Commit from each active fork at that moment. The Commit() method returns a new DataStore, representing a new moment in history.
type LocalDataStore struct {
DataStoreCommon
}
func newLocalDataStore(cs chunks.ChunkStore) *LocalDataStore {
rootRef := cs.Root()
if rootRef == (ref.Ref{}) {
return &LocalDataStore{DataStoreCommon{cs, nil}}
}
return &LocalDataStore{DataStoreCommon{cs, commitFromRef(rootRef, cs)}}
}
func newDataStoreInternal(cs chunks.ChunkStore) DataStoreCommon {
if (cs.Root() == ref.Ref{}) {
return DataStoreCommon{cs, nil}
}
return DataStoreCommon{cs, commitFromRef(cs.Root(), cs)}
}
func (lds *LocalDataStore) Commit(v types.Value) (DataStore, bool) {
ok := lds.commit(v)
return newLocalDataStore(lds.ChunkStore), ok
}
func (lds *LocalDataStore) CommitWithParents(v types.Value, p SetOfCommit) (DataStore, bool) {
ok := lds.commitWithParents(v, p)
return newLocalDataStore(lds.ChunkStore), ok
}