100 lines
2.8 KiB
Go
100 lines
2.8 KiB
Go
package mmm
|
|
|
|
import (
|
|
"cmp"
|
|
"fmt"
|
|
"slices"
|
|
|
|
"github.com/PowerDNS/lmdb-go/lmdb"
|
|
)
|
|
|
|
func (b *MultiMmapManager) gatherFreeRanges(txn *lmdb.Txn) (positions, error) {
|
|
cursor, err := txn.OpenCursor(b.indexId)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("failed to open cursor on indexId: %w", err)
|
|
}
|
|
defer cursor.Close()
|
|
|
|
usedPositions := make(positions, 0, 256)
|
|
for key, val, err := cursor.Get(nil, nil, lmdb.First); err == nil; key, val, err = cursor.Get(key, val, lmdb.Next) {
|
|
pos := positionFromBytes(val[0:12])
|
|
usedPositions = append(usedPositions, pos)
|
|
}
|
|
|
|
// sort used positions by start
|
|
slices.SortFunc(usedPositions, func(a, b position) int { return cmp.Compare(a.start, b.start) })
|
|
|
|
// if there is free space at the end this will simulate it
|
|
usedPositions = append(usedPositions, position{start: b.mmapfEnd, size: 0})
|
|
|
|
// calculate free ranges as gaps between used positions
|
|
freeRanges := make(positions, 0, len(usedPositions)/2)
|
|
var currentStart uint64 = 0
|
|
for _, used := range usedPositions {
|
|
if used.start > currentStart {
|
|
// gap from currentStart to pos.start
|
|
freeSize := used.start - currentStart
|
|
if freeSize > 0 {
|
|
freeRanges = append(freeRanges, position{
|
|
start: currentStart,
|
|
size: uint32(freeSize),
|
|
})
|
|
}
|
|
}
|
|
currentStart = used.start + uint64(used.size)
|
|
}
|
|
|
|
return freeRanges, nil
|
|
}
|
|
|
|
func (b *MultiMmapManager) mergeNewFreeRange(newFreeRange position) {
|
|
// use binary search to find the insertion point for the new pos
|
|
idx, exists := slices.BinarySearchFunc(b.freeRanges, newFreeRange.start, func(item position, target uint64) int {
|
|
return cmp.Compare(item.start, target)
|
|
})
|
|
|
|
if exists {
|
|
panic(fmt.Errorf("can't add free range that already exists: %s", newFreeRange))
|
|
}
|
|
|
|
deleteStart := -1
|
|
deleting := 0
|
|
|
|
// check the range immediately before
|
|
if idx > 0 {
|
|
before := b.freeRanges[idx-1]
|
|
if before.start+uint64(before.size) == newFreeRange.start {
|
|
deleteStart = idx - 1
|
|
deleting++
|
|
newFreeRange.start = before.start
|
|
newFreeRange.size = before.size + newFreeRange.size
|
|
}
|
|
}
|
|
|
|
// check the range immediately after
|
|
if idx < len(b.freeRanges) {
|
|
after := b.freeRanges[idx]
|
|
if newFreeRange.start+uint64(newFreeRange.size) == after.start {
|
|
if deleteStart == -1 {
|
|
deleteStart = idx
|
|
}
|
|
deleting++
|
|
|
|
newFreeRange.size = newFreeRange.size + after.size
|
|
}
|
|
}
|
|
|
|
switch deleting {
|
|
case 0:
|
|
// if we are not deleting anything we must insert the new free range
|
|
b.freeRanges = slices.Insert(b.freeRanges, idx, newFreeRange)
|
|
case 1:
|
|
// if we're deleting a single range, don't delete it, modify it in-place instead.
|
|
b.freeRanges[deleteStart] = newFreeRange
|
|
case 2:
|
|
// now if we're deleting two ranges, delete just one instead and modify the other in place
|
|
b.freeRanges[deleteStart] = newFreeRange
|
|
b.freeRanges = slices.Delete(b.freeRanges, deleteStart+1, deleteStart+1+1)
|
|
}
|
|
}
|