Commit 78f2f88b authored by Kirill Smelkov's avatar Kirill Smelkov

X wcfs/xbtree: Fix treediff(a, ø)

treediff(ø, b) is TODO
parent c32055fc
...@@ -10,7 +10,7 @@ require ( ...@@ -10,7 +10,7 @@ require (
github.com/pkg/errors v0.9.1 github.com/pkg/errors v0.9.1
github.com/stretchr/testify v1.7.0 github.com/stretchr/testify v1.7.0
lab.nexedi.com/kirr/go123 v0.0.0-20210302025843-863c4602a230 lab.nexedi.com/kirr/go123 v0.0.0-20210302025843-863c4602a230
lab.nexedi.com/kirr/neo/go v0.0.0-20210503113049-7fba56df234c lab.nexedi.com/kirr/neo/go v0.0.0-20210524152903-d02d65559752
) )
// we use kirr/go-fuse@y/nodefs-cancel // we use kirr/go-fuse@y/nodefs-cancel
......
...@@ -193,3 +193,5 @@ lab.nexedi.com/kirr/neo/go v0.0.0-20210326102715-dc35035cdc18 h1:ysqIq6WqSMhCPbS ...@@ -193,3 +193,5 @@ lab.nexedi.com/kirr/neo/go v0.0.0-20210326102715-dc35035cdc18 h1:ysqIq6WqSMhCPbS
lab.nexedi.com/kirr/neo/go v0.0.0-20210326102715-dc35035cdc18/go.mod h1:llI3hcJJMACe+rYuXUfS5dljjwIrlBMfJ1ZeRcey96A= lab.nexedi.com/kirr/neo/go v0.0.0-20210326102715-dc35035cdc18/go.mod h1:llI3hcJJMACe+rYuXUfS5dljjwIrlBMfJ1ZeRcey96A=
lab.nexedi.com/kirr/neo/go v0.0.0-20210503113049-7fba56df234c h1:+M4xtOKZqy7oC6L9CzdXi77PNI9KnohOEnEsHlmAPPE= lab.nexedi.com/kirr/neo/go v0.0.0-20210503113049-7fba56df234c h1:+M4xtOKZqy7oC6L9CzdXi77PNI9KnohOEnEsHlmAPPE=
lab.nexedi.com/kirr/neo/go v0.0.0-20210503113049-7fba56df234c/go.mod h1:llI3hcJJMACe+rYuXUfS5dljjwIrlBMfJ1ZeRcey96A= lab.nexedi.com/kirr/neo/go v0.0.0-20210503113049-7fba56df234c/go.mod h1:llI3hcJJMACe+rYuXUfS5dljjwIrlBMfJ1ZeRcey96A=
lab.nexedi.com/kirr/neo/go v0.0.0-20210524152903-d02d65559752 h1:knRAqs0xLytZrxWHkCccg9xyAbAgzGFnyHE2rdg7onI=
lab.nexedi.com/kirr/neo/go v0.0.0-20210524152903-d02d65559752/go.mod h1:llI3hcJJMACe+rYuXUfS5dljjwIrlBMfJ1ZeRcey96A=
...@@ -92,11 +92,16 @@ func (S PPTreeSubSet) AddPath(path []zodb.Oid) { ...@@ -92,11 +92,16 @@ func (S PPTreeSubSet) AddPath(path []zodb.Oid) {
panic("empty path") panic("empty path")
} }
// don't keep track of artificial empty tree
if l == 1 && path[0] == zodb.InvalidOid {
return
}
// don't explicitly keep track of embedded buckets - they all have // don't explicitly keep track of embedded buckets - they all have
// InvalidOid, and thus, if kept in S, e.g. T/B1:a and another // InvalidOid, and thus, if kept in S, e.g. T/B1:a and another
// T/B2:b would lead to InvalidOid having multiple parents. // T/B2:b would lead to InvalidOid having multiple parents.
if l >= 2 && path[l-1] == zodb.InvalidOid { if l == 2 && path[1] == zodb.InvalidOid {
path = path[:l-1] path = path[:1]
} }
parent := zodb.InvalidOid parent := zodb.InvalidOid
...@@ -268,11 +273,16 @@ func (A PPTreeSubSet) fixup(δnchild map[zodb.Oid]int) { ...@@ -268,11 +273,16 @@ func (A PPTreeSubSet) fixup(δnchild map[zodb.Oid]int) {
A.xfixup(+1, δnchild) A.xfixup(+1, δnchild)
} }
func (A PPTreeSubSet) xfixup(sign int, δnchild map[zodb.Oid]int) { func (A PPTreeSubSet) xfixup(sign int, δnchild map[zodb.Oid]int) {
//fmt.Printf("\nfixup:\n")
//fmt.Printf(" ·: %s\n", A)
//fmt.Printf(" δ: %v\n", δnchild)
//defer fmt.Printf(" ->·: %s\n\n", A)
gcq := []zodb.Oid{} gcq := []zodb.Oid{}
for oid, δnc := range δnchild { for oid, δnc := range δnchild {
t := A[oid] // t != nil as A is PP-connected t := A[oid] // t != nil as A is PP-connected
t.nchild += sign*δnc t.nchild += sign*δnc
if t.nchild == 0 && /* not root node */t.parent != zodb.InvalidOid { if t.nchild == 0 {
gcq = append(gcq, oid) gcq = append(gcq, oid)
} }
} }
......
// Copyright (C) 2021 Nexedi SA and Contributors.
// Kirill Smelkov <kirr@nexedi.com>
//
// This program is free software: you can Use, Study, Modify and Redistribute
// it under the terms of the GNU General Public License version 3, or (at your
// option) any later version, as published by the Free Software Foundation.
//
// You can also Link and Combine this program with other software covered by
// the terms of any of the Free Software licenses or any of the Open Source
// Initiative approved licenses and Convey the resulting work. Corresponding
// source of such a combination shall include the source code for all other
// software used.
//
// This program is distributed WITHOUT ANY WARRANTY; without even the implied
// warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
//
// See COPYING file for full licensing terms.
// See https://www.nexedi.com/licensing for rationale and options.
package xbtree
import (
"testing"
"lab.nexedi.com/kirr/neo/go/zodb"
)
func TestPPTreeSubSetOps(t *testing.T) {
const (
a zodb.Oid = 0xa + iota
b
c
d
ø = zodb.InvalidOid
)
type S = PPTreeSubSet
type testEntry struct {
A, B S
Union S
Difference S
}
E := func(A, B, U, D S) testEntry {
return testEntry{A, B, U, D}
}
testv := []testEntry{
E(
S{}, // A
S{}, // B
S{}, // U
S{}), // D
E(
S{a:{ø,1}, b:{a,0}}, // A
S{a:{ø,1}, c:{a,0}}, // B
S{a:{ø,2}, b:{a,0}, c:{a,0}}, // U
S{a:{ø,1}, b:{a,0}}), // D
E(
S{a:{ø,1}, b:{a,1}, c:{b,0}}, // A
S{a:{ø,1}, b:{a,1}, d:{b,0}}, // B
S{a:{ø,1}, b:{a,2}, c:{b,0}, d:{b,0}}, // U
S{a:{ø,1}, b:{a,1}, c:{b,0}}), // D
E(
S{a:{ø,1}, b:{a,0}}, // A
S{a:{ø,1}, b:{a,0}}, // B
S{a:{ø,1}, b:{a,0}}, // U
S{}), // D
}
for _, tt := range testv {
U := tt.A.Clone()
U.UnionInplace(tt.B)
D := tt.A.Clone()
D.DifferenceInplace(tt.B)
if !U.Equal(tt.Union) {
t.Errorf("Union:\n A: %s\n B: %s\n ->u: %s\n okU: %s\n", tt.A, tt.B, U, tt.Union)
}
if !D.Equal(tt.Difference) {
t.Errorf("Difference:\n A: %s\n B: %s\n ->d: %s\n okD: %s\n", tt.A, tt.B, D, tt.Difference)
}
// XXX also verify U/D properties like (A+B)\B + (A+B)\A + (A^B) == (A+B) ?
}
}
...@@ -78,6 +78,8 @@ import ( ...@@ -78,6 +78,8 @@ import (
"lab.nexedi.com/kirr/go123/xerr" "lab.nexedi.com/kirr/go123/xerr"
"lab.nexedi.com/kirr/neo/go/zodb" "lab.nexedi.com/kirr/neo/go/zodb"
"lab.nexedi.com/nexedi/wendelin.core/wcfs/internal/xzodb"
) )
// ΔValue represents change in value. // ΔValue represents change in value.
...@@ -352,8 +354,8 @@ func treediff(ctx context.Context, root zodb.Oid, δtops SetOid, δZTC SetOid, t ...@@ -352,8 +354,8 @@ func treediff(ctx context.Context, root zodb.Oid, δtops SetOid, δZTC SetOid, t
δtrackv := []*ΔPPTreeSubSet{} δtrackv := []*ΔPPTreeSubSet{}
for top := range δtops { // XXX -> sorted? for top := range δtops { // XXX -> sorted?
a, err1 := zgetNode(ctx, zconnOld, top) a, err1 := zgetNodeOrNil(ctx, zconnOld, top)
b, err2 := zgetNode(ctx, zconnNew, top) b, err2 := zgetNodeOrNil(ctx, zconnNew, top)
err := xerr.Merge(err1, err2) err := xerr.Merge(err1, err2)
if err != nil { if err != nil {
return nil, nil, err return nil, nil, err
...@@ -410,7 +412,7 @@ func treediff(ctx context.Context, root zodb.Oid, δtops SetOid, δZTC SetOid, t ...@@ -410,7 +412,7 @@ func treediff(ctx context.Context, root zodb.Oid, δtops SetOid, δZTC SetOid, t
// consistent with b (= a + δ). // consistent with b (= a + δ).
func diffX(ctx context.Context, a, b Node, δZTC SetOid, trackSet PPTreeSubSet, holeIdx treeSetKey) (δ map[Key]ΔValue, δtrack *ΔPPTreeSubSet, err error) { func diffX(ctx context.Context, a, b Node, δZTC SetOid, trackSet PPTreeSubSet, holeIdx treeSetKey) (δ map[Key]ΔValue, δtrack *ΔPPTreeSubSet, err error) {
if a==nil && b==nil { if a==nil && b==nil {
panic("BUG: both a & b == nil") panic("BUG: both a & b == nil") // XXX -> not a bug e.g. for `ø ø T` sequence?
} }
var aT, bT *Tree var aT, bT *Tree
...@@ -462,8 +464,13 @@ func diffT(ctx context.Context, A, B *Tree, δZTC SetOid, trackSet PPTreeSubSet, ...@@ -462,8 +464,13 @@ func diffT(ctx context.Context, A, B *Tree, δZTC SetOid, trackSet PPTreeSubSet,
tracef(" diffT %s %s\n", xidOf(A), xidOf(B)) tracef(" diffT %s %s\n", xidOf(A), xidOf(B))
defer xerr.Contextf(&err, "diffT %s %s", xidOf(A), xidOf(B)) defer xerr.Contextf(&err, "diffT %s %s", xidOf(A), xidOf(B))
if A == nil { panic("A is nil") } if A == nil { panic("A is nil") } // XXX -> art. ø tree
if B == nil { panic("B is nil") } Bempty := false
if B == nil {
// artificial empty tree
B = zodb.NewPersistent(reflect.TypeOf(Tree{}), /*jar*/nil).(*Tree)
Bempty = true
}
δ = map[Key]ΔValue{} δ = map[Key]ΔValue{}
δtrack = &ΔPPTreeSubSet{Del: PPTreeSubSet{}, Add: PPTreeSubSet{}, δnchildNonLeafs: map[zodb.Oid]int{}} δtrack = &ΔPPTreeSubSet{Del: PPTreeSubSet{}, Add: PPTreeSubSet{}, δnchildNonLeafs: map[zodb.Oid]int{}}
...@@ -505,7 +512,9 @@ func diffT(ctx context.Context, A, B *Tree, δZTC SetOid, trackSet PPTreeSubSet, ...@@ -505,7 +512,9 @@ func diffT(ctx context.Context, A, B *Tree, δZTC SetOid, trackSet PPTreeSubSet,
// {} oid -> parent for all nodes in Bv: current and previously expanded - up till top B // {} oid -> parent for all nodes in Bv: current and previously expanded - up till top B
// XXX requires A.oid == B.oid // XXX requires A.oid == B.oid
BtrackSet := PPTreeSubSet{} BtrackSet := PPTreeSubSet{}
if !Bempty {
BtrackSet.AddPath(trackSet.Path(B.POid())) BtrackSet.AddPath(trackSet.Path(B.POid()))
}
// phase 1: expand A top->down driven by δZTC. // phase 1: expand A top->down driven by δZTC.
// by default a node contributes to δ- // by default a node contributes to δ-
...@@ -574,14 +583,17 @@ func diffT(ctx context.Context, A, B *Tree, δZTC SetOid, trackSet PPTreeSubSet, ...@@ -574,14 +583,17 @@ func diffT(ctx context.Context, A, B *Tree, δZTC SetOid, trackSet PPTreeSubSet,
if blo != bhi_ { if blo != bhi_ {
break break
} }
_, ok := blo.node.(*Tree) bloT, ok := blo.node.(*Tree)
if !ok { if !ok {
break // bucket break // bucket
} }
err = blo.node.PActivate(ctx); /*X*/if err != nil { return nil,nil, err } err = bloT.PActivate(ctx); /*X*/if err != nil { return nil,nil, err }
defer blo.node.PDeactivate() defer bloT.PDeactivate()
if len(bloT.Entryv()) == 0 {
break // empty tree
}
// XXX check for empty tree?
bchildren := Bv.Expand(blo) bchildren := Bv.Expand(blo)
for _, bc := range bchildren { for _, bc := range bchildren {
bcOid := bc.node.POid() bcOid := bc.node.POid()
...@@ -841,11 +853,15 @@ func diffB(ctx context.Context, a, b *Bucket) (δ map[Key]ΔValue, err error) { ...@@ -841,11 +853,15 @@ func diffB(ctx context.Context, a, b *Bucket) (δ map[Key]ΔValue, err error) {
return δ, nil return δ, nil
} }
// zgetNode returns btree node corresponding to zconn.Get(oid) . // zgetNodeOrNil returns btree node corresponding to zconn.Get(oid) .
func zgetNode(ctx context.Context, zconn *zodb.Connection, oid zodb.Oid) (_ Node, err error) { // if the node does not exist, (nil, ok) is returned.
func zgetNodeOrNil(ctx context.Context, zconn *zodb.Connection, oid zodb.Oid) (_ Node, err error) {
defer xerr.Contextf(&err, "getnode %s@%s", oid, zconn.At()) defer xerr.Contextf(&err, "getnode %s@%s", oid, zconn.At())
xnode, err := zconn.Get(ctx, oid) xnode, err := zconn.Get(ctx, oid)
if err != nil { if err != nil {
if xzodb.IsErrNoData(err) {
err = nil
}
return nil, err return nil, err
} }
...@@ -854,6 +870,19 @@ func zgetNode(ctx context.Context, zconn *zodb.Connection, oid zodb.Oid) (_ Node ...@@ -854,6 +870,19 @@ func zgetNode(ctx context.Context, zconn *zodb.Connection, oid zodb.Oid) (_ Node
return nil, fmt.Errorf("unexpected type: %s", zodb.ClassOf(xnode)) return nil, fmt.Errorf("unexpected type: %s", zodb.ClassOf(xnode))
} }
// activate the node to find out it really exists
// after removal on storage, the object might have stayed in Connection
// cache due to e.g. PCachePinObject, and it will be PActivate that
// will return "deleted" error.
err = node.PActivate(ctx)
if err != nil {
if xzodb.IsErrNoData(err) {
return nil, nil
}
return nil, err
}
node.PDeactivate()
return node, nil return node, nil
} }
......
...@@ -342,8 +342,10 @@ func (rbs RBucketSet) trackSet(tracked SetKey) PPTreeSubSet { ...@@ -342,8 +342,10 @@ func (rbs RBucketSet) trackSet(tracked SetKey) PPTreeSubSet {
ppoid = p.parent.oid ppoid = p.parent.oid
} }
pt, already := trackSet[p.oid] // skip ø (non-existing) tree
if p.oid != zodb.InvalidOid {
newParent := false newParent := false
pt, already := trackSet[p.oid]
if !already { if !already {
pt = &nodeInTree{parent: ppoid, nchild: 0} pt = &nodeInTree{parent: ppoid, nchild: 0}
trackSet[p.oid] = pt trackSet[p.oid] = pt
...@@ -356,9 +358,11 @@ func (rbs RBucketSet) trackSet(tracked SetKey) PPTreeSubSet { ...@@ -356,9 +358,11 @@ func (rbs RBucketSet) trackSet(tracked SetKey) PPTreeSubSet {
if newNode { if newNode {
pt.nchild++ pt.nchild++
} }
p = p.parent
newNode = newParent newNode = newParent
} }
p = p.parent
}
} }
return trackSet return trackSet
} }
...@@ -610,10 +614,7 @@ func xverifyΔBTail_Update(t *testing.T, subj string, db *zodb.DB, treeRoot zodb ...@@ -610,10 +614,7 @@ func xverifyΔBTail_Update(t *testing.T, subj string, db *zodb.DB, treeRoot zodb
t.Run(fmt.Sprintf("Update/%s→%s", t1.tree, t2.tree), func(t *testing.T) { t.Run(fmt.Sprintf("Update/%s→%s", t1.tree, t2.tree), func(t *testing.T) {
if t1.tree == DEL { if t1.tree == DEL {
// Track(k) -> Update ; requires that tree exists at Track time // Track(k) -> Update ; requires that tree exists at Track time
t.Skip("Track(tree1) needs tree1 to exist") t.Skip("Track(tree1) needs tree1 to exist") // XXX just limit trackKeys to ø instead ?
}
if t2.tree == DEL {
t.Fatal("TODO")
} }
allKeys := allTestKeys(t1, t2) allKeys := allTestKeys(t1, t2)
...@@ -1320,6 +1321,10 @@ func TestΔBTail(t *testing.T) { ...@@ -1320,6 +1321,10 @@ func TestΔBTail(t *testing.T) {
// test known cases going through tree1 -> tree2 -> ... // test known cases going through tree1 -> tree2 -> ...
testv := []interface{} { testv := []interface{} {
// start from non-empty tree to verify both ->empty and empty-> transitions
"T/B1:a,2:b",
// empty
"T/B:", "T/B:",
// +1 // +1
......
...@@ -22,6 +22,7 @@ package xzodb ...@@ -22,6 +22,7 @@ package xzodb
import ( import (
"context" "context"
"errors"
"fmt" "fmt"
"lab.nexedi.com/kirr/go123/xcontext" "lab.nexedi.com/kirr/go123/xcontext"
...@@ -80,3 +81,18 @@ func ZOpen(ctx context.Context, zdb *zodb.DB, zopt *zodb.ConnOptions) (_ *ZConn, ...@@ -80,3 +81,18 @@ func ZOpen(ctx context.Context, zdb *zodb.DB, zopt *zodb.ConnOptions) (_ *ZConn,
TxnCtx: txnCtx, TxnCtx: txnCtx,
}, nil }, nil
} }
// IsErrNoData returns whether err is due to NoDataError or NoObjectError.
func IsErrNoData(err error) bool {
var eNoData *zodb.NoDataError
var eNoObject *zodb.NoObjectError
switch {
case errors.As(err, &eNoData):
return true
case errors.As(err, &eNoObject):
return true
default:
return false
}
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment