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
|
prefix netip.Prefix
|
||||||
lo *tree
|
lo *tree
|
||||||
hi *tree
|
hi *tree
|
||||||
parent *tree
|
|
||||||
minfreelen int
|
minfreelen int
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -36,9 +35,6 @@ func (t *tree) Walk(f func(*tree) bool) {
|
||||||
}
|
}
|
||||||
t.hi.Walk(f)
|
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 {
|
func (t *tree) Find(p netip.Prefix) *tree {
|
||||||
best := t.FindBestPrefix(p)
|
best := t.FindBestPrefix(p)
|
||||||
|
@ -65,6 +61,106 @@ func (t *tree) FindBestPrefix(p netip.Prefix) *tree {
|
||||||
}
|
}
|
||||||
return t
|
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 {
|
func (t *tree) FindBestPrefixLen(bits int) *tree {
|
||||||
if t == nil {
|
if t == nil {
|
||||||
return nil
|
return nil
|
||||||
|
@ -103,8 +199,8 @@ func (t *tree) FindBestPrefixLen(bits int) *tree {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tree) Alloc(bits int) *tree {
|
func (t *tree) Alloc(bits int) *tree {
|
||||||
//log.Println("Searching space for", bits)
|
pp := t.FindBestPrefixLenPath(bits)
|
||||||
best := t.FindBestPrefixLen(bits)
|
best := pp.top()
|
||||||
if best == nil {
|
if best == nil {
|
||||||
best = t
|
best = t
|
||||||
}
|
}
|
||||||
|
@ -112,30 +208,27 @@ func (t *tree) Alloc(bits int) *tree {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
tofix := best
|
|
||||||
|
|
||||||
// the first insert can be into a new hi branch
|
// the first insert can be into a new hi branch
|
||||||
if best.lo != nil {
|
if best.lo != nil {
|
||||||
best.hi = &tree{
|
best.hi = &tree{
|
||||||
prefix: SplitPrefixHi(best.prefix),
|
prefix: SplitPrefixHi(best.prefix),
|
||||||
parent: best,
|
|
||||||
minfreelen: best.prefix.Bits() + 2,
|
minfreelen: best.prefix.Bits() + 2,
|
||||||
}
|
}
|
||||||
best = best.hi
|
best = best.hi
|
||||||
|
pp = pp.push(best)
|
||||||
}
|
}
|
||||||
|
|
||||||
// the rest of the inserts are always into the lo branch
|
// the rest of the inserts are always into the lo branch
|
||||||
for best.prefix.Bits() < bits {
|
for best.prefix.Bits() < bits {
|
||||||
best.lo = &tree{
|
best.lo = &tree{
|
||||||
prefix: SplitPrefixLo(best.prefix),
|
prefix: SplitPrefixLo(best.prefix),
|
||||||
parent: best,
|
|
||||||
minfreelen: best.prefix.Bits() + 2,
|
minfreelen: best.prefix.Bits() + 2,
|
||||||
}
|
}
|
||||||
best = best.lo
|
best = best.lo
|
||||||
|
pp = pp.push(best)
|
||||||
}
|
}
|
||||||
|
|
||||||
best.fixMinFree()
|
fixMinFreeAll(pp)
|
||||||
tofix.fixMinFreeAll()
|
|
||||||
|
|
||||||
return best
|
return best
|
||||||
}
|
}
|
||||||
|
@ -159,16 +252,19 @@ func (t *tree) fixMinFree() {
|
||||||
|
|
||||||
t.minfreelen = minfreelen
|
t.minfreelen = minfreelen
|
||||||
}
|
}
|
||||||
func (t *tree) fixMinFreeAll() {
|
func fixMinFreeAll(pp prefixpath) {
|
||||||
if t == nil {
|
var root *tree
|
||||||
return
|
if len(pp) > 0 {
|
||||||
|
root = pp[0]
|
||||||
}
|
}
|
||||||
|
|
||||||
fixfree := t
|
for len(pp) > 0 {
|
||||||
|
var t *tree
|
||||||
for fixfree != nil {
|
pp, t = pp.pop()
|
||||||
fixfree.fixMinFree()
|
t.fixMinFree()
|
||||||
fixfree = fixfree.parent
|
}
|
||||||
|
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 {
|
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
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
for t.prefix.Bits() < p.Bits() {
|
for pp.top().prefix.Bits() < p.Bits() {
|
||||||
if lo := SplitPrefixLo(t.prefix); lo.Overlaps(p) {
|
top := pp.top()
|
||||||
t.lo = &tree{prefix: lo, parent: t}
|
if lo := SplitPrefixLo(top.prefix); lo.Overlaps(p) {
|
||||||
t = t.lo
|
top.lo = &tree{prefix: lo}
|
||||||
} else if hi := SplitPrefixHi(t.prefix); hi.Overlaps(p) {
|
pp = pp.push(top.lo)
|
||||||
t.hi = &tree{prefix: hi, parent: t}
|
} else if hi := SplitPrefixHi(top.prefix); hi.Overlaps(p) {
|
||||||
t = t.hi
|
top.hi = &tree{prefix: hi}
|
||||||
|
pp = pp.push(top.hi)
|
||||||
} else {
|
} else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
t.fixMinFreeAll()
|
fixMinFreeAll(pp)
|
||||||
return t
|
return pp.top()
|
||||||
}
|
}
|
||||||
|
|
||||||
type ErrNotFound struct{}
|
type ErrNotFound struct{}
|
||||||
|
@ -214,28 +315,35 @@ func (e ErrNotEmpty) Error() string {
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *tree) Dealloc(p netip.Prefix) error {
|
func (t *tree) Dealloc(p netip.Prefix) error {
|
||||||
match := t.Find(p)
|
pp := t.FindPath(p)
|
||||||
if match == nil {
|
if pp == nil {
|
||||||
return ErrNotFound{}
|
return ErrNotFound{}
|
||||||
}
|
}
|
||||||
if !match.Leaf() {
|
if !pp.top().Leaf() {
|
||||||
return ErrNotEmpty{}
|
return ErrNotEmpty{}
|
||||||
}
|
}
|
||||||
|
|
||||||
for match != nil {
|
child := pp.top()
|
||||||
if match.Leaf() && match.parent != nil {
|
for len(pp) > 0 {
|
||||||
if match.parent.lo == match {
|
var top *tree
|
||||||
match.parent.lo = nil
|
pp, top = pp.pop()
|
||||||
|
if child.Leaf() {
|
||||||
|
if top.lo == child {
|
||||||
|
top.lo = nil
|
||||||
}
|
}
|
||||||
if match.parent.hi == match {
|
if top.hi == child {
|
||||||
match.parent.hi = nil
|
top.hi = nil
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
match.fixMinFree()
|
top.fixMinFree()
|
||||||
match = match.parent
|
child = top
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// fixup root
|
||||||
|
if t.Leaf() {
|
||||||
|
t.minfreelen = t.prefix.Bits() + 1
|
||||||
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -264,6 +372,31 @@ func (t *tree) Dot() string {
|
||||||
return buf.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 {
|
type DB struct {
|
||||||
sync.Mutex
|
sync.Mutex
|
||||||
provisions *tree
|
provisions *tree
|
||||||
|
@ -286,7 +419,7 @@ func (db *DB) Used() []netip.Prefix {
|
||||||
defer db.Unlock()
|
defer db.Unlock()
|
||||||
var ret []netip.Prefix
|
var ret []netip.Prefix
|
||||||
db.provisions.Walk(func(t *tree) bool {
|
db.provisions.Walk(func(t *tree) bool {
|
||||||
if t.Leaf() && t.parent != nil {
|
if t.Leaf() && t != db.provisions {
|
||||||
ret = append(ret, t.prefix)
|
ret = append(ret, t.prefix)
|
||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
|
@ -299,7 +432,7 @@ func (db *DB) UsedGrouped() [][]netip.Prefix {
|
||||||
defer db.Unlock()
|
defer db.Unlock()
|
||||||
bins := make(map[int][]netip.Prefix)
|
bins := make(map[int][]netip.Prefix)
|
||||||
db.provisions.Walk(func(t *tree) bool {
|
db.provisions.Walk(func(t *tree) bool {
|
||||||
if t.Leaf() && t.parent != nil {
|
if t.Leaf() && t != db.provisions {
|
||||||
p := t.prefix
|
p := t.prefix
|
||||||
bins[p.Bits()] = append(bins[p.Bits()], p)
|
bins[p.Bits()] = append(bins[p.Bits()], p)
|
||||||
}
|
}
|
||||||
|
|
|
@ -49,6 +49,17 @@ func TestTreeAllocSmall(t *testing.T) {
|
||||||
|
|
||||||
for _, test := range tests {
|
for _, test := range tests {
|
||||||
//t.Log("searching for", test.bits)
|
//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)
|
best := root.Alloc(test.bits)
|
||||||
if best == nil {
|
if best == nil {
|
||||||
log.Print(root.Dot())
|
log.Print(root.Dot())
|
||||||
|
@ -139,11 +150,13 @@ func TestTreeDealloc(t *testing.T) {
|
||||||
if err := root.Dealloc(first); err != nil {
|
if err := root.Dealloc(first); err != nil {
|
||||||
t.Fatal("failed to remove", first)
|
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 {
|
root.Walk(func(t *tree) bool {
|
||||||
log.Println(t.prefix, t.Leaf())
|
log.Println(t.prefix, t.Leaf())
|
||||||
return true
|
return true
|
||||||
})
|
})
|
||||||
|
t.Log("Expected", first, "got", again)
|
||||||
|
t.Log(root.Dot())
|
||||||
t.Fatal(first)
|
t.Fatal(first)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -174,6 +187,7 @@ func TestFindBestPrefix(t *testing.T) {
|
||||||
best := [][2]string{
|
best := [][2]string{
|
||||||
{"128.0.0.0/32", "128.0.0.0/1"},
|
{"128.0.0.0/32", "128.0.0.0/1"},
|
||||||
{"128.0.0.1/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 {
|
for _, test := range best {
|
||||||
|
@ -183,6 +197,16 @@ func TestFindBestPrefix(t *testing.T) {
|
||||||
if best.prefix != want {
|
if best.prefix != want {
|
||||||
t.Fatal(best, 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)
|
p := netip.MustParsePrefix(test)
|
||||||
insert := root.Insert(p)
|
insert := root.Insert(p)
|
||||||
if insert == nil {
|
if insert == nil {
|
||||||
|
t.Log("unable to find space for", p, "in", root)
|
||||||
|
t.Log(root.Dot())
|
||||||
t.Fatal(root, test)
|
t.Fatal(root, test)
|
||||||
}
|
}
|
||||||
match := root.Find(p)
|
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 {
|
if err := root.Dealloc(netip.MustParsePrefix("0.0.0.0/31")); err != nil {
|
||||||
t.Fatalf("%s", err)
|
t.Fatalf("%s", err)
|
||||||
}
|
}
|
||||||
|
//fmt.Print(root.Dot())
|
||||||
expectAlloc(t, root, 32, "0.0.0.5/32")
|
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.0/32")
|
||||||
expectAlloc(t, root, 32, "0.0.0.1/32")
|
expectAlloc(t, root, 32, "0.0.0.1/32")
|
||||||
|
|
Loading…
Reference in New Issue