Commit ac3bb8cc authored by Kirill Smelkov's avatar Kirill Smelkov

.

parent eb715051
// Copyright (C) 2018-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 main
// PP-connected subset of tree nodes.
import (
"fmt"
"lab.nexedi.com/kirr/neo/go/zodb"
)
// XXX is a set of PP(leafs) nodes XXX -> PTreeSubSet ? TreePSubSet ? TreePPSet ? PPSubSet ?
// XXX PPNodeSet? TreePPSubSet ? PPSet? PPNodeSet? PPOidSet?
// ~~ PPOidSet ~~ <- N
//
// YYY:
// XXX PPTreeNodeSet
// XXX PPTreeNodeObjSet
// XXX PPTreeNodeOidSet
// XXX PPTreeObjSet
// XXX PPTreeOidSet
//
// where PP maps a leaf to {leaf, leaf.parent, leaf.parent.parent, ...} up to
// top root node from where the leaf is reached.
//
// Every node in the set also has .parent pointer.
//
// XXX place
type trackIndex map[zodb.Oid]*nodeTrack
// XXX place XXX PTreeSubSetNode ? PPNode? nodeP? nodePnC ?
// nodeTrack represents tracking information about a node.
type nodeTrack struct {
parent zodb.Oid // parent node | InvalidOid for root
nchild int // number of direct children in trackIdx referring to this node
// XXX + [lo,hi) range this node is coming under in its parent
/*
holes SetKey // missing keys tracked under this node; nil for !leaf
// XXX move holes into separate ΔBtail..holeIdx
*/
}
// δtrackIndex represents change to trackIndex. XXX name
//
// XXX refer to trackIndex.ApplyΔ
//
// The result B of applying δ to A is:
//
// B = A.xDifference(δ.Del).xUnion(δ.Add) (*)
//
// (*) NOTE δ.Del and δ.Add might have their leafs starting from non-leaf nodes in A/B.
// This situation arises when δ represents a change in path to particular
// node, but that node itself does not change, for example:
//
// c* c
// / \ /
// 41* 42 41
// | | | \
// 22 43 46 43
// | | |
// 44 22 44
//
// Here nodes {c, 41} are changed, node 42 is unlinked, and node 46 is added.
// Nodes 43 and 44 stay unchanged.
//
// δ.Del = c-42-43 | c-41-22
// δ.Add = c-41-43 | c-41-46-22
//
// The second component with "-22" builds from leaf, but the first
// component with "-43" builds from non-leaf node.
//
// δnchildNonLeafs = {43: +1}
//
// Only complete result of applying all
//
// - xfixup(-1, δnchildNonLeafs)
// - δ.Del,
// - δ.Add, and
// - xfixup(+1, δnchildNonLeafs)
//
// produce correctly PP-connected set.
//
// XXX place
type δtrackIndex struct {
Del trackIndex
Add trackIndex
δnchildNonLeafs map[zodb.Oid]int
}
// Update updates δ to be combination of δ+δ2.
func (δ *δtrackIndex) Update(δ2 *δtrackIndex) {
δ.Del.UnionInplace(δ2.Del)
δ.Add.UnionInplace(δ2.Add)
for oid, δnc := range δ2.δnchildNonLeafs {
δ.δnchildNonLeafs[oid] += δnc
}
}
// Reverse changes δ=diff(A->B) to δ'=diff(A<-B).
func (δ *δtrackIndex) Reverse() {
δ.Del, δ.Add = δ.Add, δ.Del
// δnchildNonLeafs stays the same
}
// gc1 garbage-collects oid and cleans up its parent down-up.
func (tidx trackIndex) gc1(oid zodb.Oid) {
t, present := tidx[oid]
if !present {
return // already not there
}
if t.nchild != 0 {
panicf("gc %s %v (nchild != 0)", oid, t)
}
delete(tidx, oid)
oid = t.parent
for oid != zodb.InvalidOid {
t := tidx[oid]
t.nchild--
if t.nchild > 0 || /* root node */t.parent == zodb.InvalidOid {
break
}
delete(tidx, oid)
oid = t.parent
}
}
// verify verifies internal consistency of tidx.
func (tidx trackIndex) verify() {
// XXX !debug -> return
var badv []string
badf := func(format string, argv ...interface{}) {
badv = append(badv, fmt.Sprintf(format, argv...))
}
defer func() {
if badv != nil {
emsg := fmt.Sprintf("tidx.verify: fail:\n\n")
for _, bad := range badv {
emsg += fmt.Sprintf("- %s\n", bad)
}
emsg += fmt.Sprintf("\ntidx: %s\n", tidx)
panic(emsg)
}
}()
// recompute {} oid -> children and verify .nchild against it
children := map[zodb.Oid]SetOid{}
for oid, t := range tidx {
if t.parent != zodb.InvalidOid {
cc, ok := children[t.parent]
if !ok {
cc = SetOid{}
children[t.parent] = cc
}
cc.Add(oid)
}
}
for oid, t := range tidx {
cc := children[oid]
if t.nchild != len(cc) {
badf("[%s].nchild=%d children: %s", oid, t.nchild, cc)
}
}
// verify that all pointed-to parents are present in tidx
for oid := range children {
_, ok := tidx[oid]
if !ok {
badf("oid %s is pointed to via some .parent, but is not present in the set", oid)
}
}
}
// DifferenceInplace sets A = PP(A.leafs \ B.leafs)
//
// In other words it removes B nodes from A while still maintaining A as P-connected.
func (A trackIndex) DifferenceInplace(B trackIndex) {
if debugPPSet {
fmt.Printf("\n\nDifferenceInplace:\n")
fmt.Printf(" A: %s\n", A)
fmt.Printf(" B: %s\n", B)
defer fmt.Printf("->D: %s\n", A)
}
A.verify()
B.verify()
defer A.verify()
A.xDifferenceInplace(B)
}
func (A trackIndex) xDifferenceInplace(B trackIndex) {
if debugPPSet {
fmt.Printf("\n\n xDifferenceInplace:\n")
fmt.Printf(" a: %s\n", A)
fmt.Printf(" b: %s\n", B)
defer fmt.Printf(" ->d: %s\n", A)
}
δnchild := map[zodb.Oid]int{}
// remove B.leafs and their parents
for oid, t2 := range B {
if t2.nchild != 0 {
continue // not a leaf
}
t, present := A[oid]
if !present {
continue // already not there
}
if t2.parent != t.parent {
// XXX or verify this at Track time and require
// that update is passed only entries with the
// same .parent? (then it would be ok to panic here)
// XXX -> error (e.g. due to corrupt data in ZODB)
panicf("node %s is reachable from multiple parents: %s %s",
oid, t.parent, t2.parent)
}
delete(A, oid)
if t.parent != zodb.InvalidOid {
δnchild[t.parent] -= 1
}
}
A.fixup(δnchild)
}
func (A trackIndex) xUnionInplace(B trackIndex) {
if debugPPSet {
fmt.Printf("\n\n xUnionInplace:\n")
fmt.Printf(" a: %s\n", A)
fmt.Printf(" b: %s\n", B)
defer fmt.Printf(" ->u: %s\n", A)
}
δnchild := map[zodb.Oid]int{}
for oid, t2 := range B {
t, already := A[oid]
if !already {
t = &nodeTrack{parent: t2.parent, nchild: 0}
A[oid] = t
// remember to nchild++ in parent
if t.parent != zodb.InvalidOid {
δnchild[t.parent] += 1
}
} else {
if t2.parent != t.parent {
// XXX or verify this at Track time and require
// that update is passed only entries with the
// same .parent? (then it would be ok to panic here)
// XXX -> error (e.g. due to corrupt data in ZODB)
panicf("node %s is reachable from multiple parents: %s %s",
oid, t.parent, t2.parent)
}
}
}
A.fixup(δnchild)
}
// fixup performs scheduled δnchild adjustment.
// XXX place
func (A trackIndex) fixup(δnchild map[zodb.Oid]int) {
A.xfixup(+1, δnchild)
}
func (A trackIndex) xfixup(sign int, δnchild map[zodb.Oid]int) {
gcq := []zodb.Oid{}
for oid, δnc := range δnchild {
t := A[oid] // XXX t can be nil -> XXX no must be there as A is connected
t.nchild += sign*δnc
if t.nchild == 0 && /* not root node */t.parent != zodb.InvalidOid {
gcq = append(gcq, oid)
}
}
// GC parents that became to have .nchild == 0
for _, oid := range gcq {
A.gc1(oid)
}
}
// UnionInplace sets A = PP(A.leafs | B.leafs)
//
// In other words it adds B nodes to A.
func (A trackIndex) UnionInplace(B trackIndex) {
if debugPPSet {
fmt.Printf("\n\nUnionInplace:\n")
fmt.Printf(" A: %s\n", A)
fmt.Printf(" B: %s\n", B)
defer fmt.Printf("->U: %s\n", A)
}
A.verify()
B.verify()
defer A.verify()
A.xUnionInplace(B)
}
// ApplyΔ applies δ to trackIdx. XXX
func (tidx trackIndex) ApplyΔ(δ *δtrackIndex) {
if debugPPSet {
fmt.Printf("\n\nApplyΔ\n")
fmt.Printf(" A: %s\n", tidx)
fmt.Printf(" -: %s\n", δ.Del)
fmt.Printf(" +: %s\n", δ.Add)
fmt.Printf(" x: %v\n", δ.δnchildNonLeafs)
defer fmt.Printf("\n->B: %s\n", tidx)
}
tidx.verify()
δ.Del.verify()
δ.Add.verify()
defer tidx.verify()
tidx.xfixup(-1, δ.δnchildNonLeafs)
tidx.xDifferenceInplace(δ.Del)
tidx.xUnionInplace(δ.Add)
tidx.xfixup(+1, δ.δnchildNonLeafs)
}
// Path returns path leading to node specified by oid.
//
// The node must be in the set.
// XXX place
func (tidx trackIndex) Path(oid zodb.Oid) (path []zodb.Oid) {
for {
t, ok := tidx[oid]
if !ok {
panicf("node %s is not in the set <- %v", oid, path)
}
path = append([]zodb.Oid{oid}, path...)
oid = t.parent
if oid == zodb.InvalidOid {
break
}
}
return path
}
// XXX place
// XXX doc
func (tidx trackIndex) AddNodePath(path []Node) { // XXX Tree|Bucket; path[0] = root
// XXX assert Tree Tree ... Tree Bucket
// root := path[0].(*Tree).POid()
oidv := []zodb.Oid{}
for _, node := range path {
oidv = append(oidv, node.POid())
}
tidx.AddPath(oidv)
}
func (tidx trackIndex) AddPath(path []zodb.Oid) {
tidx.verify()
defer tidx.verify()
l := len(path)
if l == 0 {
panic("empty path")
}
// don't explicitly keep track of embedded buckets - they all have
// InvalidOid, and thus, if kept in tidx, e.g. T/B1:a and another
// T/B2:b would lead to InvalidOid having multiple parents.
if l >= 2 && path[l-1] == zodb.InvalidOid {
path = path[:l-1]
}
parent := zodb.InvalidOid
var ptrack *nodeTrack = nil
var track *nodeTrack // XXX kill here
var oldTrack bool
for _, oid := range path {
if oid == zodb.InvalidOid {
panicf("path has node with invalid oid: %v", path)
}
track, oldTrack = tidx[oid]
if !oldTrack {
track = &nodeTrack{parent: parent, nchild: 0} // XXX
/*
if i == l-1 { // leaf
track.holes = SetKey{}
}
*/
tidx[oid] = track
// XXX .trackNew += oid
}
if track.parent != parent {
// XXX -> error (e.g. due to corrupt data in ZODB)
panicf("node %s is reachable from multiple parents: %s %s",
oid, track.parent, parent)
}
if ptrack != nil && !oldTrack {
ptrack.nchild++
}
parent = oid
ptrack = track
}
}
// XXX place
func (orig trackIndex) Clone() trackIndex {
klon := make(trackIndex, len(orig))
for oid, t := range orig {
klon[oid] = &nodeTrack{parent: t.parent, nchild: t.nchild}
}
return klon
}
const debugPPSet = false
......@@ -217,317 +217,6 @@ type ΔTree struct {
}
// XXX is a set of PP(leafs) nodes XXX -> PTreeSubSet ? TreePSubSet ? TreePPSet ? PPSubSet ?
// XXX PPNodeSet? TreePPSubSet ? PPSet? PPNodeSet? PPOidSet?
// ~~ PPOidSet ~~ <- Y
//
// where PP maps a leaf to {leaf, leaf.parent, leaf.parent.parent, ...} up to
// top root node from where the leaf is reached.
//
// Every node in the set also has .parent pointer.
//
// XXX place
type trackIndex map[zodb.Oid]*nodeTrack
// XXX place XXX PTreeSubSetNode ? PPNode? nodeP? nodePnC ?
// nodeTrack represents tracking information about a node.
type nodeTrack struct {
parent zodb.Oid // parent node | InvalidOid for root
nchild int // number of direct children in trackIdx referring to this node
/*
holes SetKey // missing keys tracked under this node; nil for !leaf
// XXX move holes into separate ΔBtail..holeIdx
*/
}
// δtrackIndex represents change to trackIndex. XXX name
//
// XXX refer to trackIndex.ApplyΔ
//
// The result B of applying δ to A is:
//
// B = A.xDifference(δ.Del).xUnion(δ.Add) (*)
//
// (*) NOTE δ.Del and δ.Add might have their leafs starting from non-leaf nodes in A/B.
// This situation arises when δ represents a change in path to particular
// node, but that node itself does not change, for example:
//
// c* c
// / \ /
// 41* 42 41
// | | | \
// 22 43 46 43
// | | |
// 44 22 44
//
// Here nodes {c, 41} are changed, node 42 is unlinked, and node 46 is added.
// Nodes 43 and 44 stay unchanged.
//
// δ.Del = c-42-43 | c-41-22
// δ.Add = c-41-43 | c-41-46-22
//
// The second component with "-22" builds from leaf, but the first
// component with "-43" builds from non-leaf node.
//
// δnchildNonLeafs = {43: +1}
//
// Only complete result of applying all
//
// - xfixup(-1, δnchildNonLeafs)
// - δ.Del,
// - δ.Add, and
// - xfixup(+1, δnchildNonLeafs)
//
// produce correctly PP-connected set.
//
// XXX place
type δtrackIndex struct {
Del trackIndex
Add trackIndex
δnchildNonLeafs map[zodb.Oid]int
}
// Update updates δ to be combination of δ+δ2.
func (δ *δtrackIndex) Update(δ2 *δtrackIndex) {
δ.Del.UnionInplace(δ2.Del)
δ.Add.UnionInplace(δ2.Add)
for oid, δnc := range δ2.δnchildNonLeafs {
δ.δnchildNonLeafs[oid] += δnc
}
}
// Reverse changes δ=diff(A->B) to δ'=diff(A<-B).
func (δ *δtrackIndex) Reverse() {
δ.Del, δ.Add = δ.Add, δ.Del
// δnchildNonLeafs stays the same
}
// gc1 garbage-collects oid and cleans up its parent down-up.
func (tidx trackIndex) gc1(oid zodb.Oid) {
t, present := tidx[oid]
if !present {
return // already not there
}
if t.nchild != 0 {
panicf("gc %s %v (nchild != 0)", oid, t)
}
delete(tidx, oid)
oid = t.parent
for oid != zodb.InvalidOid {
t := tidx[oid]
t.nchild--
if t.nchild > 0 || /* root node */t.parent == zodb.InvalidOid {
break
}
delete(tidx, oid)
oid = t.parent
}
}
// verify verifies internal consistency of tidx.
func (tidx trackIndex) verify() {
// XXX !debug -> return
var badv []string
badf := func(format string, argv ...interface{}) {
badv = append(badv, fmt.Sprintf(format, argv...))
}
defer func() {
if badv != nil {
emsg := fmt.Sprintf("tidx.verify: fail:\n\n")
for _, bad := range badv {
emsg += fmt.Sprintf("- %s\n", bad)
}
emsg += fmt.Sprintf("\ntidx: %s\n", tidx)
panic(emsg)
}
}()
// recompute {} oid -> children and verify .nchild against it
children := map[zodb.Oid]SetOid{}
for oid, t := range tidx {
if t.parent != zodb.InvalidOid {
cc, ok := children[t.parent]
if !ok {
cc = SetOid{}
children[t.parent] = cc
}
cc.Add(oid)
}
}
for oid, t := range tidx {
cc := children[oid]
if t.nchild != len(cc) {
badf("[%s].nchild=%d children: %s", oid, t.nchild, cc)
}
}
// verify that all pointed-to parents are present in tidx
for oid := range children {
_, ok := tidx[oid]
if !ok {
badf("oid %s is pointed to via some .parent, but is not present in the set", oid)
}
}
}
const debugPPSet = false
// DifferenceInplace sets A = PP(A.leafs \ B.leafs)
//
// In other words it removes B nodes from A while still maintaining A as P-connected.
func (A trackIndex) DifferenceInplace(B trackIndex) {
if debugPPSet {
fmt.Printf("\n\nDifferenceInplace:\n")
fmt.Printf(" A: %s\n", A)
fmt.Printf(" B: %s\n", B)
defer fmt.Printf("->D: %s\n", A)
}
A.verify()
B.verify()
defer A.verify()
A.xDifferenceInplace(B)
}
func (A trackIndex) xDifferenceInplace(B trackIndex) {
if debugPPSet {
fmt.Printf("\n\n xDifferenceInplace:\n")
fmt.Printf(" a: %s\n", A)
fmt.Printf(" b: %s\n", B)
defer fmt.Printf(" ->d: %s\n", A)
}
δnchild := map[zodb.Oid]int{}
// remove B.leafs and their parents
for oid, t2 := range B {
if t2.nchild != 0 {
continue // not a leaf
}
t, present := A[oid]
if !present {
continue // already not there
}
if t2.parent != t.parent {
// XXX or verify this at Track time and require
// that update is passed only entries with the
// same .parent? (then it would be ok to panic here)
// XXX -> error (e.g. due to corrupt data in ZODB)
panicf("node %s is reachable from multiple parents: %s %s",
oid, t.parent, t2.parent)
}
delete(A, oid)
if t.parent != zodb.InvalidOid {
δnchild[t.parent] -= 1
}
}
A.fixup(δnchild)
}
func (A trackIndex) xUnionInplace(B trackIndex) {
if debugPPSet {
fmt.Printf("\n\n xUnionInplace:\n")
fmt.Printf(" a: %s\n", A)
fmt.Printf(" b: %s\n", B)
defer fmt.Printf(" ->u: %s\n", A)
}
δnchild := map[zodb.Oid]int{}
for oid, t2 := range B {
t, already := A[oid]
if !already {
t = &nodeTrack{parent: t2.parent, nchild: 0}
A[oid] = t
// remember to nchild++ in parent
if t.parent != zodb.InvalidOid {
δnchild[t.parent] += 1
}
} else {
if t2.parent != t.parent {
// XXX or verify this at Track time and require
// that update is passed only entries with the
// same .parent? (then it would be ok to panic here)
// XXX -> error (e.g. due to corrupt data in ZODB)
panicf("node %s is reachable from multiple parents: %s %s",
oid, t.parent, t2.parent)
}
}
}
A.fixup(δnchild)
}
// fixup performs scheduled δnchild adjustment.
// XXX place
func (A trackIndex) fixup(δnchild map[zodb.Oid]int) {
A.xfixup(+1, δnchild)
}
func (A trackIndex) xfixup(sign int, δnchild map[zodb.Oid]int) {
gcq := []zodb.Oid{}
for oid, δnc := range δnchild {
t := A[oid] // XXX t can be nil -> XXX no must be there as A is connected
t.nchild += sign*δnc
if t.nchild == 0 && /* not root node */t.parent != zodb.InvalidOid {
gcq = append(gcq, oid)
}
}
// GC parents that became to have .nchild == 0
for _, oid := range gcq {
A.gc1(oid)
}
}
// UnionInplace sets A = PP(A.leafs | B.leafs)
//
// In other words it adds B nodes to A.
func (A trackIndex) UnionInplace(B trackIndex) {
if debugPPSet {
fmt.Printf("\n\nUnionInplace:\n")
fmt.Printf(" A: %s\n", A)
fmt.Printf(" B: %s\n", B)
defer fmt.Printf("->U: %s\n", A)
}
A.verify()
B.verify()
defer A.verify()
A.xUnionInplace(B)
}
// ApplyΔ applies δ to trackIdx. XXX
func (tidx trackIndex) ApplyΔ(δ *δtrackIndex) {
if debugPPSet {
fmt.Printf("\n\nApplyΔ\n")
fmt.Printf(" A: %s\n", tidx)
fmt.Printf(" -: %s\n", δ.Del)
fmt.Printf(" +: %s\n", δ.Add)
fmt.Printf(" x: %v\n", δ.δnchildNonLeafs)
defer fmt.Printf("\n->B: %s\n", tidx)
}
tidx.verify()
δ.Del.verify()
δ.Add.verify()
defer tidx.verify()
tidx.xfixup(-1, δ.δnchildNonLeafs)
tidx.xDifferenceInplace(δ.Del)
tidx.xUnionInplace(δ.Add)
tidx.xfixup(+1, δ.δnchildNonLeafs)
}
// treeSetKey represents ordered set of keys.
// it can be point-queried and range-accessed.
// TODO -> btree
......@@ -575,16 +264,9 @@ func (orig *ΔBtail) clone() *ΔBtail {
klon.δZtail.Append(δZ.Rev, δZ.Changev)
}
// trackIdx
klon.trackIdx = make(trackIndex, len(orig.trackIdx))
for oid, t := range orig.trackIdx {
klon.trackIdx[oid] = &nodeTrack{parent: t.parent, nchild: t.nchild}
}
// trackNew
klon.trackNew = make(trackIndex, len(orig.trackNew))
for oid, t := range orig.trackNew {
klon.trackNew[oid] = &nodeTrack{parent: t.parent, nchild: t.nchild}
}
// trackIdx, trackNew
klon.trackIdx = orig.trackIdx.Clone()
klon.trackNew = orig.trackNew.Clone()
// byRoot
klon.byRoot = make(map[zodb.Oid]*ΔTtail, len(orig.byRoot))
......@@ -694,92 +376,6 @@ func (δBtail *ΔBtail) holeIdxFor(root zodb.Oid) treeSetKey {
return holeIdx
}
// Path returns path leading to node specified by oid.
//
// The node must be in the set.
// XXX place
func (tidx trackIndex) Path(oid zodb.Oid) (path []zodb.Oid) {
for {
t, ok := tidx[oid]
if !ok {
panicf("node %s is not in the set <- %v", oid, path)
}
path = append([]zodb.Oid{oid}, path...)
oid = t.parent
if oid == zodb.InvalidOid {
break
}
}
return path
}
// XXX place
// XXX doc
func (tidx trackIndex) AddNodePath(path []Node) { // XXX Tree|Bucket; path[0] = root
// XXX assert Tree Tree ... Tree Bucket
// root := path[0].(*Tree).POid()
oidv := []zodb.Oid{}
for _, node := range path {
oidv = append(oidv, node.POid())
}
tidx.AddPath(oidv)
}
func (tidx trackIndex) AddPath(path []zodb.Oid) {
tidx.verify()
defer tidx.verify()
l := len(path)
if l == 0 {
panic("empty path")
}
// don't explicitly keep track of embedded buckets - they all have
// InvalidOid, and thus, if kept in tidx, e.g. T/B1:a and another
// T/B2:b would lead to InvalidOid having multiple parents.
if l >= 2 && path[l-1] == zodb.InvalidOid {
path = path[:l-1]
}
parent := zodb.InvalidOid
var ptrack *nodeTrack = nil
var track *nodeTrack // XXX kill here
var oldTrack bool
for _, oid := range path {
if oid == zodb.InvalidOid {
panicf("path has node with invalid oid: %v", path)
}
track, oldTrack = tidx[oid]
if !oldTrack {
track = &nodeTrack{parent: parent, nchild: 0} // XXX
/*
if i == l-1 { // leaf
track.holes = SetKey{}
}
*/
tidx[oid] = track
// XXX .trackNew += oid
}
if track.parent != parent {
// XXX -> error (e.g. due to corrupt data in ZODB)
panicf("node %s is reachable from multiple parents: %s %s",
oid, track.parent, parent)
}
if ptrack != nil && !oldTrack {
ptrack.nchild++
}
parent = oid
ptrack = track
}
}
// rebuild rebuilds ΔBtail taking trackNew requests into account.
// XXX place
func (δBtail *ΔBtail) rebuild() (err error) {
......
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