From 8b28d4fbba0c524cb3d10dfedc358936798c26cb Mon Sep 17 00:00:00 2001 From: Rafael Weinstein Date: Tue, 16 Jun 2015 15:10:12 -0700 Subject: [PATCH] Added Reachable and correct calculation of current roots --- commit/commit.go | 35 +++++++++++++------- commit/commit_test.go | 53 ++++++++++++++++++++++++++---- commit/memcache_reachable.go | 62 ++++++++++++++++++++++++++++++++++++ enc/read_value.go | 7 ++++ 4 files changed, 138 insertions(+), 19 deletions(-) create mode 100644 commit/memcache_reachable.go diff --git a/commit/commit.go b/commit/commit.go index a7fad23aa3..1f82976c51 100644 --- a/commit/commit.go +++ b/commit/commit.go @@ -8,10 +8,15 @@ import ( "github.com/attic-labs/noms/types" ) +type Reachable interface { + IsSupercededFrom(candidate, root ref.Ref) bool +} + type Commit struct { - root store.RootStore - source store.ChunkSource - sink store.ChunkSink + root store.RootStore + source store.ChunkSource + sink store.ChunkSink + reachable Reachable } func (c *Commit) GetRoots() (currentRoots types.Set) { @@ -20,9 +25,7 @@ func (c *Commit) GetRoots() (currentRoots types.Set) { return types.NewSet() } - rootSetValue, err := enc.ReadValue(rootRef, c.source) - Chk.NoError(err) - return rootSetValue.(types.Set) + return enc.MustReadValue(rootRef, c.source).(types.Set) } func (c *Commit) Commit(newRoots types.Set) { @@ -42,18 +45,26 @@ func (c *Commit) Commit(newRoots types.Set) { } func (c *Commit) doCommit(add, remove types.Set) bool { + oldRoot := c.root.Root() oldRoots := c.GetRoots() - oldRef := oldRoots.Ref() - if oldRoots.Len() == 0 { - oldRef = ref.Ref{} + + prexisting := make([]types.Value, 0) + add.Iter(func(r types.Value) (stop bool) { + if c.reachable.IsSupercededFrom(r.Ref(), oldRoot) { + prexisting = append(prexisting, r) + } + return false + }) + add = add.Remove(prexisting...) + if add.Len() == 0 { + return true } - newRoots := oldRoots.Subtract(remove) - newRoots = newRoots.Union(add) + newRoots := oldRoots.Subtract(remove).Union(add) // TODO(rafael): This set will be orphaned if this UpdateRoot below fails newRef, err := enc.WriteValue(newRoots, c.sink) Chk.NoError(err) - return c.root.UpdateRoot(newRef, oldRef) + return c.root.UpdateRoot(newRef, oldRoot) } diff --git a/commit/commit_test.go b/commit/commit_test.go index 44b9ca1892..a0e34dcacb 100644 --- a/commit/commit_test.go +++ b/commit/commit_test.go @@ -18,7 +18,12 @@ func TestCommit(t *testing.T) { assert.NoError(err) store := store.NewFileStore(dir, "root") - commit := &Commit{store, store, store} + commit := &Commit{ + store, + store, + store, + NewMemCacheReachable(store), + } roots := commit.GetRoots() assert.Equal(uint64(0), roots.Len()) @@ -29,7 +34,6 @@ func TestCommit(t *testing.T) { types.NewString("value"), types.NewString("a"), ) aSet := types.NewSet(a) - enc.WriteValue(aSet, store) commit.Commit(aSet) assert.Equal(commit.GetRoots(), aSet) @@ -39,7 +43,6 @@ func TestCommit(t *testing.T) { types.NewString("value"), types.NewString("b"), ) bSet := types.NewSet(b) - enc.WriteValue(bSet, store) commit.Commit(bSet) assert.Equal(commit.GetRoots(), bSet) @@ -50,7 +53,6 @@ func TestCommit(t *testing.T) { types.NewString("value"), types.NewString("c"), ) cSet := types.NewSet(c) - enc.WriteValue(cSet, store) commit.Commit(cSet) bcSet := bSet.Insert(c) assert.Equal(commit.GetRoots(), bcSet) @@ -77,7 +79,6 @@ func TestCommit(t *testing.T) { types.NewString("value"), types.NewString("e"), ) eSet := types.NewSet(e) - enc.WriteValue(eSet, store) commit.Commit(eSet) deSet := dSet.Insert(e) assert.Equal(commit.GetRoots(), deSet) @@ -85,14 +86,52 @@ func TestCommit(t *testing.T) { // |a| <- |b| <-- |e| <- |f| // \----|c| <--/ / // \---|d| <-------/ - f := types.NewMap( types.NewString("parents"), deSet, types.NewString("value"), types.NewString("f"), ) fSet := types.NewSet(f) - enc.WriteValue(fSet, store) commit.Commit(fSet) assert.Equal(commit.GetRoots(), fSet) + + // Attempt to recommit |b| + commit.Commit(bSet) + assert.Equal(commit.GetRoots(), fSet) + + // Attempt to recommit |f| + commit.Commit(fSet) + assert.Equal(commit.GetRoots(), fSet) + + // Attempt to recommit |c| while committing |g| + // |a| <- |b| <-- |e| <- |f| <- |g| + // \----|c| <--/ / / + // \---|d| <-------/------/ + fdSet := fSet.Insert(d) + g := types.NewMap( + types.NewString("parents"), fdSet, + types.NewString("value"), types.NewString("g"), + ) + gSet := types.NewSet(g) + gdSet := gSet.Insert(c) + + commit.Commit(gdSet) + assert.Equal(commit.GetRoots(), gSet) + + // / -|h| + // / | + // |a| <- |b| <-- |e| <- |f| <- |g| + // \----|c| <--/ / / + // \---|d| <-------/------/ + abSet := aSet.Insert(b) + h := types.NewMap( + types.NewString("parents"), abSet, + types.NewString("value"), types.NewString("h"), + ) + hSet := types.NewSet(h) + + commit.Commit(hSet) + hgSet := hSet.Insert(g) + roots = commit.GetRoots() + assert.Equal(commit.GetRoots(), hgSet) } diff --git a/commit/memcache_reachable.go b/commit/memcache_reachable.go new file mode 100644 index 0000000000..5e20342f8b --- /dev/null +++ b/commit/memcache_reachable.go @@ -0,0 +1,62 @@ +package commit + +import ( + . "github.com/attic-labs/noms/dbg" + "github.com/attic-labs/noms/enc" + "github.com/attic-labs/noms/ref" + "github.com/attic-labs/noms/store" + "github.com/attic-labs/noms/types" +) + +// TODO(rafael): +type memcacheReachable struct { + source store.ChunkSource + refs map[string]bool +} + +func (cache *memcacheReachable) updateFromCommitMap(commitMap types.Map) { + refString := commitMap.Ref().String() + if _, ok := cache.refs[refString]; ok { + return + } + + parents := commitMap.Get(types.NewString("parents")).(types.Set) + parents.Iter(func(commit types.Value) (stop bool) { + cache.updateFromCommitMap(commit.(types.Map)) + return false + }) + cache.refs[refString] = true +} + +func (cache *memcacheReachable) updateFromRootSet(rootRef ref.Ref) { + refString := rootRef.String() + if _, ok := cache.refs[refString]; ok { + return + } + + // TODO(rafael): This is super-inefficient with eager ReadValue and no caching + rootSet := enc.MustReadValue(rootRef, cache.source).(types.Set) + rootSet.Iter(func(commit types.Value) (stop bool) { + cache.updateFromCommitMap(commit.(types.Map)) + return false + }) + cache.refs[refString] = true +} + +func (cache *memcacheReachable) IsSupercededFrom(candidate, root ref.Ref) bool { + Chk.NotEqual(candidate, root) + if (root == ref.Ref{}) { + return false + } + + cache.updateFromRootSet(root) + _, ok := cache.refs[candidate.String()] + return ok +} + +func NewMemCacheReachable(source store.ChunkSource) Reachable { + return &memcacheReachable{ + source, + make(map[string]bool), + } +} diff --git a/enc/read_value.go b/enc/read_value.go index 58b6f1a1d0..69d7651265 100644 --- a/enc/read_value.go +++ b/enc/read_value.go @@ -5,6 +5,7 @@ import ( "bytes" "fmt" + . "github.com/attic-labs/noms/dbg" "github.com/attic-labs/noms/ref" "github.com/attic-labs/noms/store" "github.com/attic-labs/noms/types" @@ -34,3 +35,9 @@ func ReadValue(ref ref.Ref, cs store.ChunkSource) (types.Value, error) { return nil, fmt.Errorf("Unsupported chunk tag: %+v", prefix) } + +func MustReadValue(ref ref.Ref, cs store.ChunkSource) types.Value { + val, err := ReadValue(ref, cs) + Chk.NoError(err) + return val +}