Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG/CHANGELOG-1.5.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,5 +7,6 @@
- [Remove the unused txs list](https://github.com/etcd-io/bbolt/pull/973)
- [Add option `NoStatistics` to make the statistics optional](https://github.com/etcd-io/bbolt/pull/977)
- [Move panic handling from goroutine to the parent function](https://github.com/etcd-io/bbolt/pull/1153)
- [Recover from panics in tx.check](https://github.com/etcd-io/bbolt/pull/1160)

<hr>
5 changes: 5 additions & 0 deletions tx_check.go
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,11 @@ func (tx *Tx) Check(options ...CheckOption) <-chan error {
}

func (tx *Tx) check(cfg checkConfig, ch chan error) {
defer func() {
if r := recover(); r != nil {
ch <- panicked{r}
}
}()
// Force loading free list if opened in ReadOnly mode.
tx.db.loadFreelist()

Expand Down
49 changes: 49 additions & 0 deletions tx_check_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -120,6 +120,43 @@ func TestTx_Check_WithNestBucket(t *testing.T) {
db.MustClose()
}

func TestTx_Check_Panic(t *testing.T) {
bucketName := []byte("data")
t.Log("Creating db file.")
db := btesting.MustCreateDBWithOption(t, &bbolt.Options{PageSize: 4096})

// Each page can hold roughly 20 key/values pair, so 100 such
// key/value pairs will consume about 5 leaf pages.
err := db.Fill(bucketName, 1, 100,
func(tx int, k int) []byte { return []byte(fmt.Sprintf("%04d", k)) },
func(tx int, k int) []byte { return make([]byte, 100) },
)
require.NoError(t, err)

corruptRootPage(t, db.DB, bucketName)

path := db.Path()

require.NoError(t, db.Close())

db = btesting.MustOpenDBWithOption(t, path, &bbolt.Options{PageSize: 4096})

vErr := db.View(func(tx *bbolt.Tx) error {
errChan := tx.Check()
for cErr := range errChan {
fmt.Println("cErr", cErr)
return cErr
}
return nil
})
require.Error(t, vErr)
require.ErrorContains(t, vErr, "has unexpected type/flags: 0")

// Manually close the db, otherwise the PostTestCleanup will
// check the db again and accordingly fail the test.
db.MustClose()
}

// corruptRandomLeafPage corrupts one random leaf page.
func corruptRandomLeafPageInBucket(t testing.TB, db *bbolt.DB, bucketName []byte) (victimPageId common.Pgid, validPageIds []common.Pgid) {
bucketRootPageId := mustGetBucketRootPage(t, db, bucketName)
Expand Down Expand Up @@ -152,6 +189,18 @@ func corruptRandomLeafPageInBucket(t testing.TB, db *bbolt.DB, bucketName []byte
return victimPageId, validPageIds
}

func corruptRootPage(t testing.TB, db *bbolt.DB, bucketName []byte) {
bucketRootPageId := mustGetBucketRootPage(t, db, bucketName)
bucketRootPage, bucketRootPageBuf, err := guts_cli.ReadPage(db.Path(), uint64(bucketRootPageId))
require.NoError(t, err)
require.True(t, bucketRootPage.IsBranchPage())

bucketRootPage.SetFlags(0)

err = guts_cli.WritePage(db.Path(), bucketRootPageBuf)
require.NoError(t, err)
}

// mustGetBucketRootPage returns the root page for the provided bucket.
func mustGetBucketRootPage(t testing.TB, db *bbolt.DB, bucketName []byte) common.Pgid {
var rootPageId common.Pgid
Expand Down