Pages stacktrace should be provided for initialization errors.

It makes it easy to find which page actually looks to be corrupted.
```
go build ./cmd/bbolt/ && ./bbolt check ~/Downloads/db
panic: freepages: failed to get all reachable pages (page 8314893338927566090: out of bounds: 6258 (stack: [4517 395 821]))

goroutine 18 [running]:
go.etcd.io/bbolt.(*DB).freepages.func2()
        /Users/ptab/gits/bbolt/db.go:1056 +0x8c
created by go.etcd.io/bbolt.(*DB).freepages
        /Users/ptab/gits/bbolt/db.go:1054 +0x138
```

Signed-off-by: Piotr Tabor <ptab@google.com>
pull/357/head
Piotr Tabor 2022-12-17 15:32:54 +01:00
parent 1d5a2b0e24
commit 7190a8fe57
2 changed files with 18 additions and 12 deletions

View File

@ -402,7 +402,7 @@ func (b *Bucket) Stats() BucketStats {
if b.root == 0 {
s.InlineBucketN += 1
}
b.forEachPage(func(p *page, depth int) {
b.forEachPage(func(p *page, depth int, pgstack []pgid) {
if (p.flags & leafPageFlag) != 0 {
s.KeyN += int(p.count)
@ -477,15 +477,15 @@ func (b *Bucket) Stats() BucketStats {
}
// forEachPage iterates over every page in a bucket, including inline pages.
func (b *Bucket) forEachPage(fn func(*page, int)) {
func (b *Bucket) forEachPage(fn func(*page, int, []pgid)) {
// If we have an inline page then just use that.
if b.page != nil {
fn(b.page, 0)
fn(b.page, 0, []pgid{b.root})
return
}
// Otherwise traverse the page hierarchy.
b.tx.forEachPage(b.root, 0, fn)
b.tx.forEachPage(b.root, fn)
}
// forEachPageNode iterates over every page (or node) in a bucket.

22
tx.go
View File

@ -461,16 +461,16 @@ func (tx *Tx) checkBucket(b *Bucket, reachable map[pgid]*page, freed map[pgid]bo
}
// Check every page used by this bucket.
b.tx.forEachPage(b.root, 0, func(p *page, _ int) {
b.tx.forEachPage(b.root, func(p *page, _ int, stack []pgid) {
if p.id > tx.meta.pgid {
ch <- fmt.Errorf("page %d: out of bounds: %d", int(p.id), int(b.tx.meta.pgid))
ch <- fmt.Errorf("page %d: out of bounds: %d (stack: %v)", int(p.id), int(b.tx.meta.pgid), stack)
}
// Ensure each page is only referenced once.
for i := pgid(0); i <= pgid(p.overflow); i++ {
var id = p.id + i
if _, ok := reachable[id]; ok {
ch <- fmt.Errorf("page %d: multiple references", int(id))
ch <- fmt.Errorf("page %d: multiple references (stack: %v)", int(id), stack)
}
reachable[id] = p
}
@ -479,7 +479,7 @@ func (tx *Tx) checkBucket(b *Bucket, reachable map[pgid]*page, freed map[pgid]bo
if freed[p.id] {
ch <- fmt.Errorf("page %d: reachable freed", int(p.id))
} else if (p.flags&branchPageFlag) == 0 && (p.flags&leafPageFlag) == 0 {
ch <- fmt.Errorf("page %d: invalid type: %s", int(p.id), p.typ())
ch <- fmt.Errorf("page %d: invalid type: %s (stack: %v)", int(p.id), p.typ(), stack)
}
})
@ -618,17 +618,23 @@ func (tx *Tx) page(id pgid) *page {
}
// forEachPage iterates over every page within a given page and executes a function.
func (tx *Tx) forEachPage(pgid pgid, depth int, fn func(*page, int)) {
p := tx.page(pgid)
func (tx *Tx) forEachPage(pgidnum pgid, fn func(*page, int, []pgid)) {
stack := make([]pgid, 10)
stack[0] = pgidnum
tx.forEachPageInternal(stack[:1], fn)
}
func (tx *Tx) forEachPageInternal(pgidstack []pgid, fn func(*page, int, []pgid)) {
p := tx.page(pgidstack[len(pgidstack)-1])
// Execute function.
fn(p, depth)
fn(p, len(pgidstack)-1, pgidstack)
// Recursively loop over children.
if (p.flags & branchPageFlag) != 0 {
for i := 0; i < int(p.count); i++ {
elem := p.branchPageElement(uint16(i))
tx.forEachPage(elem.pgid, depth+1, fn)
tx.forEachPageInternal(append(pgidstack, elem.pgid), fn)
}
}
}