tree: remove explicit parent pointer
- remove the explicit parent pointer - instead, keep track of parents while diving down the tree - this is in preparation to convert all operations to copy-on-write
This commit is contained in:
parent
83ae602c23
commit
3b3299139d
|
@ -13,7 +13,6 @@ type tree struct {
|
|||
prefix netip.Prefix
|
||||
lo *tree
|
||||
hi *tree
|
||||
parent *tree
|
||||
minfreelen int
|
||||
}
|
||||
|
||||
|
@ -36,9 +35,6 @@ func (t *tree) Walk(f func(*tree) bool) {
|
|||
}
|
||||
t.hi.Walk(f)
|
||||
}
|
||||
func (t *tree) haveSpace() bool {
|
||||
return t != nil && !t.Full() && (t.parent == nil || !t.Leaf())
|
||||
}
|
||||
|
||||
func (t *tree) Find(p netip.Prefix) *tree {
|
||||
best := t.FindBestPrefix(p)
|
||||
|
@ -65,6 +61,106 @@ func (t *tree) FindBestPrefix(p netip.Prefix) *tree {
|
|||
}
|
||||
return t
|
||||
}
|
||||
|
||||
func (t *tree) FindPath(p netip.Prefix) prefixpath {
|
||||
best := t.FindBestPrefixPath(p)
|
||||
if best.top() != nil && best.top().prefix == p {
|
||||
return best
|
||||
}
|
||||
return nil
|
||||
}
|
||||
func (t *tree) FindBestPrefixPath(p netip.Prefix) prefixpath {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
|
||||
pp := make(prefixpath, 0, t.prefix.Addr().BitLen())
|
||||
pp = pp.push(t)
|
||||
cur := t
|
||||
|
||||
for {
|
||||
switch {
|
||||
case cur.lo != nil && cur.lo.prefix.Overlaps(p):
|
||||
cur = cur.lo
|
||||
pp = pp.push(cur)
|
||||
case cur.hi != nil && cur.hi.prefix.Overlaps(p):
|
||||
cur = cur.hi
|
||||
pp = pp.push(cur)
|
||||
default:
|
||||
return pp
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
func (t *tree) FindBestPrefixLenPath(bits int) prefixpath {
|
||||
return t.findBestPrefixLenPath(bits, make(prefixpath, 0, t.prefix.Addr().BitLen()))
|
||||
}
|
||||
func (t *tree) findBestPrefixLenPath(bits int, pp prefixpath) prefixpath {
|
||||
if t == nil {
|
||||
return nil
|
||||
}
|
||||
if t.Leaf() {
|
||||
// already taken
|
||||
return nil
|
||||
}
|
||||
if t.minfreelen > bits {
|
||||
return nil
|
||||
}
|
||||
if t.prefix.Bits() >= bits {
|
||||
return nil
|
||||
}
|
||||
|
||||
pp = append(pp, t)
|
||||
|
||||
lobest := t.lo.findBestPrefixLenPath(bits, pp)
|
||||
if lobest != nil && lobest.top().minfreelen == bits {
|
||||
// found exact match with with the lowest possible address
|
||||
// take it and don't search in high branches
|
||||
return lobest
|
||||
}
|
||||
|
||||
if lobest == nil {
|
||||
// reuse pp and safe on allocations
|
||||
hibest := t.hi.findBestPrefixLenPath(bits, pp)
|
||||
|
||||
if hibest != nil {
|
||||
return hibest
|
||||
}
|
||||
return pp
|
||||
} else {
|
||||
// need to clip pp, so lower path isn't overwritten
|
||||
hibest := t.hi.findBestPrefixLenPath(bits, slices.Clip(pp))
|
||||
|
||||
switch {
|
||||
case hibest == nil:
|
||||
return lobest
|
||||
case lobest.top().minfreelen >= hibest.top().minfreelen:
|
||||
return lobest
|
||||
}
|
||||
return hibest
|
||||
}
|
||||
}
|
||||
|
||||
func allowedMinFreeLen(t *tree, bits int) bool {
|
||||
if t == nil {
|
||||
return false
|
||||
}
|
||||
return t.minfreelen <= bits
|
||||
}
|
||||
|
||||
func cmpMinFree(t1, t2 *tree) int {
|
||||
if diff := t1.minfree() - t2.minfree(); diff != 0 {
|
||||
return diff
|
||||
}
|
||||
return t1.prefix.Addr().Compare(t1.prefix.Addr())
|
||||
}
|
||||
func (t *tree) minfree() int {
|
||||
if t == nil {
|
||||
return 128 + 1
|
||||
}
|
||||
return t.minfreelen
|
||||
}
|
||||
|
||||
func (t *tree) FindBestPrefixLen(bits int) *tree {
|
||||
if t == nil {
|
||||
return nil
|
||||
|
@ -103,8 +199,8 @@ func (t *tree) FindBestPrefixLen(bits int) *tree {
|
|||
}
|
||||
|
||||
func (t *tree) Alloc(bits int) *tree {
|
||||
//log.Println("Searching space for", bits)
|
||||
best := t.FindBestPrefixLen(bits)
|
||||
pp := t.FindBestPrefixLenPath(bits)
|
||||
best := pp.top()
|
||||
if best == nil {
|
||||
best = t
|
||||
}
|
||||
|
@ -112,30 +208,27 @@ func (t *tree) Alloc(bits int) *tree {
|
|||
return nil
|
||||
}
|
||||
|
||||
tofix := best
|
||||
|
||||
// the first insert can be into a new hi branch
|
||||
if best.lo != nil {
|
||||
best.hi = &tree{
|
||||
prefix: SplitPrefixHi(best.prefix),
|
||||
parent: best,
|
||||
minfreelen: best.prefix.Bits() + 2,
|
||||
}
|
||||
best = best.hi
|
||||
pp = pp.push(best)
|
||||
}
|
||||
|
||||
// the rest of the inserts are always into the lo branch
|
||||
for best.prefix.Bits() < bits {
|
||||
best.lo = &tree{
|
||||
prefix: SplitPrefixLo(best.prefix),
|
||||
parent: best,
|
||||
minfreelen: best.prefix.Bits() + 2,
|
||||
}
|
||||
best = best.lo
|
||||
pp = pp.push(best)
|
||||
}
|
||||
|
||||
best.fixMinFree()
|
||||
tofix.fixMinFreeAll()
|
||||
fixMinFreeAll(pp)
|
||||
|
||||
return best
|
||||
}
|
||||
|
@ -159,16 +252,19 @@ func (t *tree) fixMinFree() {
|
|||
|
||||
t.minfreelen = minfreelen
|
||||
}
|
||||
func (t *tree) fixMinFreeAll() {
|
||||
if t == nil {
|
||||
return
|
||||
func fixMinFreeAll(pp prefixpath) {
|
||||
var root *tree
|
||||
if len(pp) > 0 {
|
||||
root = pp[0]
|
||||
}
|
||||
|
||||
fixfree := t
|
||||
|
||||
for fixfree != nil {
|
||||
fixfree.fixMinFree()
|
||||
fixfree = fixfree.parent
|
||||
for len(pp) > 0 {
|
||||
var t *tree
|
||||
pp, t = pp.pop()
|
||||
t.fixMinFree()
|
||||
}
|
||||
if root.Leaf() {
|
||||
root.minfreelen = root.prefix.Bits() + 1
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -180,25 +276,30 @@ func (t *tree) calcfreelen(parent *tree) int {
|
|||
}
|
||||
|
||||
func (t *tree) Insert(p netip.Prefix) *tree {
|
||||
t = t.FindBestPrefix(p)
|
||||
pp := t.FindBestPrefixPath(p)
|
||||
|
||||
if !t.haveSpace() {
|
||||
top := pp.top()
|
||||
if top != t && (top.Full() || top.Leaf()) {
|
||||
return nil
|
||||
}
|
||||
if top == t && top.Full() {
|
||||
return nil
|
||||
}
|
||||
|
||||
for t.prefix.Bits() < p.Bits() {
|
||||
if lo := SplitPrefixLo(t.prefix); lo.Overlaps(p) {
|
||||
t.lo = &tree{prefix: lo, parent: t}
|
||||
t = t.lo
|
||||
} else if hi := SplitPrefixHi(t.prefix); hi.Overlaps(p) {
|
||||
t.hi = &tree{prefix: hi, parent: t}
|
||||
t = t.hi
|
||||
for pp.top().prefix.Bits() < p.Bits() {
|
||||
top := pp.top()
|
||||
if lo := SplitPrefixLo(top.prefix); lo.Overlaps(p) {
|
||||
top.lo = &tree{prefix: lo}
|
||||
pp = pp.push(top.lo)
|
||||
} else if hi := SplitPrefixHi(top.prefix); hi.Overlaps(p) {
|
||||
top.hi = &tree{prefix: hi}
|
||||
pp = pp.push(top.hi)
|
||||
} else {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
t.fixMinFreeAll()
|
||||
return t
|
||||
fixMinFreeAll(pp)
|
||||
return pp.top()
|
||||
}
|
||||
|
||||
type ErrNotFound struct{}
|
||||
|
@ -214,28 +315,35 @@ func (e ErrNotEmpty) Error() string {
|
|||
}
|
||||
|
||||
func (t *tree) Dealloc(p netip.Prefix) error {
|
||||
match := t.Find(p)
|
||||
if match == nil {
|
||||
pp := t.FindPath(p)
|
||||
if pp == nil {
|
||||
return ErrNotFound{}
|
||||
}
|
||||
if !match.Leaf() {
|
||||
if !pp.top().Leaf() {
|
||||
return ErrNotEmpty{}
|
||||
}
|
||||
|
||||
for match != nil {
|
||||
if match.Leaf() && match.parent != nil {
|
||||
if match.parent.lo == match {
|
||||
match.parent.lo = nil
|
||||
child := pp.top()
|
||||
for len(pp) > 0 {
|
||||
var top *tree
|
||||
pp, top = pp.pop()
|
||||
if child.Leaf() {
|
||||
if top.lo == child {
|
||||
top.lo = nil
|
||||
}
|
||||
if match.parent.hi == match {
|
||||
match.parent.hi = nil
|
||||
if top.hi == child {
|
||||
top.hi = nil
|
||||
}
|
||||
}
|
||||
|
||||
match.fixMinFree()
|
||||
match = match.parent
|
||||
top.fixMinFree()
|
||||
child = top
|
||||
}
|
||||
|
||||
// fixup root
|
||||
if t.Leaf() {
|
||||
t.minfreelen = t.prefix.Bits() + 1
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
|
@ -264,6 +372,31 @@ func (t *tree) Dot() string {
|
|||
return buf.String()
|
||||
}
|
||||
|
||||
type prefixpath []*tree
|
||||
|
||||
func (pp prefixpath) empty() bool {
|
||||
return len(pp) == 0
|
||||
}
|
||||
func (pp prefixpath) push(t *tree) prefixpath {
|
||||
return append(pp, t)
|
||||
}
|
||||
func (pp prefixpath) pop() (prefixpath, *tree) {
|
||||
if pp.empty() {
|
||||
return nil, nil
|
||||
}
|
||||
lastidx := len(pp) - 1
|
||||
last := pp[lastidx]
|
||||
//pp[lastidx] = nil // breaks fixMinFreeAllPath
|
||||
|
||||
return pp[:lastidx], last
|
||||
}
|
||||
func (pp prefixpath) top() *tree {
|
||||
if pp.empty() {
|
||||
return nil
|
||||
}
|
||||
return pp[len(pp)-1]
|
||||
}
|
||||
|
||||
type DB struct {
|
||||
sync.Mutex
|
||||
provisions *tree
|
||||
|
@ -286,7 +419,7 @@ func (db *DB) Used() []netip.Prefix {
|
|||
defer db.Unlock()
|
||||
var ret []netip.Prefix
|
||||
db.provisions.Walk(func(t *tree) bool {
|
||||
if t.Leaf() && t.parent != nil {
|
||||
if t.Leaf() && t != db.provisions {
|
||||
ret = append(ret, t.prefix)
|
||||
}
|
||||
return true
|
||||
|
@ -299,7 +432,7 @@ func (db *DB) UsedGrouped() [][]netip.Prefix {
|
|||
defer db.Unlock()
|
||||
bins := make(map[int][]netip.Prefix)
|
||||
db.provisions.Walk(func(t *tree) bool {
|
||||
if t.Leaf() && t.parent != nil {
|
||||
if t.Leaf() && t != db.provisions {
|
||||
p := t.prefix
|
||||
bins[p.Bits()] = append(bins[p.Bits()], p)
|
||||
}
|
||||
|
|
|
@ -49,6 +49,17 @@ func TestTreeAllocSmall(t *testing.T) {
|
|||
|
||||
for _, test := range tests {
|
||||
//t.Log("searching for", test.bits)
|
||||
findbest := root.FindBestPrefixLen(test.bits)
|
||||
findbestpath := root.FindBestPrefixLenPath(test.bits)
|
||||
|
||||
if findbest != findbestpath.top() {
|
||||
for _, pe := range findbestpath {
|
||||
t.Log(pe.prefix)
|
||||
}
|
||||
fmt.Println(root.Dot())
|
||||
t.Fatal(findbestpath.top().prefix, findbest.prefix)
|
||||
}
|
||||
|
||||
best := root.Alloc(test.bits)
|
||||
if best == nil {
|
||||
log.Print(root.Dot())
|
||||
|
@ -139,11 +150,13 @@ func TestTreeDealloc(t *testing.T) {
|
|||
if err := root.Dealloc(first); err != nil {
|
||||
t.Fatal("failed to remove", first)
|
||||
}
|
||||
if first != root.Alloc(2).prefix {
|
||||
if again := root.Alloc(2).prefix; again != first {
|
||||
root.Walk(func(t *tree) bool {
|
||||
log.Println(t.prefix, t.Leaf())
|
||||
return true
|
||||
})
|
||||
t.Log("Expected", first, "got", again)
|
||||
t.Log(root.Dot())
|
||||
t.Fatal(first)
|
||||
}
|
||||
}
|
||||
|
@ -174,6 +187,7 @@ func TestFindBestPrefix(t *testing.T) {
|
|||
best := [][2]string{
|
||||
{"128.0.0.0/32", "128.0.0.0/1"},
|
||||
{"128.0.0.1/32", "128.0.0.0/1"},
|
||||
{"0.0.0.0/32", "0.0.0.0/32"},
|
||||
}
|
||||
|
||||
for _, test := range best {
|
||||
|
@ -183,6 +197,16 @@ func TestFindBestPrefix(t *testing.T) {
|
|||
if best.prefix != want {
|
||||
t.Fatal(best, want)
|
||||
}
|
||||
bestpath := root.FindBestPrefixPath(netip.MustParsePrefix(test[0]))
|
||||
if bestpath.top().prefix != want {
|
||||
for _, pe := range bestpath {
|
||||
t.Log(pe.prefix)
|
||||
}
|
||||
t.Fatal(bestpath.top().prefix, want)
|
||||
}
|
||||
//for _, pe := range bestpath {
|
||||
// t.Log(pe.prefix)
|
||||
//}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -201,6 +225,8 @@ func TestInsert(t *testing.T) {
|
|||
p := netip.MustParsePrefix(test)
|
||||
insert := root.Insert(p)
|
||||
if insert == nil {
|
||||
t.Log("unable to find space for", p, "in", root)
|
||||
t.Log(root.Dot())
|
||||
t.Fatal(root, test)
|
||||
}
|
||||
match := root.Find(p)
|
||||
|
@ -249,6 +275,7 @@ func TestAllocDealloc2(t *testing.T) {
|
|||
if err := root.Dealloc(netip.MustParsePrefix("0.0.0.0/31")); err != nil {
|
||||
t.Fatalf("%s", err)
|
||||
}
|
||||
//fmt.Print(root.Dot())
|
||||
expectAlloc(t, root, 32, "0.0.0.5/32")
|
||||
expectAlloc(t, root, 32, "0.0.0.0/32")
|
||||
expectAlloc(t, root, 32, "0.0.0.1/32")
|
||||
|
|
Loading…
Reference in New Issue