From 7f018f457c692ee82292be122146bf35900571a5 Mon Sep 17 00:00:00 2001 From: VihasMakwana <121151420+VihasMakwana@users.noreply.github.com> Date: Sat, 7 Mar 2026 02:23:02 +0530 Subject: [PATCH 1/4] add recover Signed-off-by: VihasMakwana <121151420+VihasMakwana@users.noreply.github.com> --- CHANGELOG/CHANGELOG-1.5.md | 2 ++ tx_check.go | 5 ++++ tx_check_test.go | 49 ++++++++++++++++++++++++++++++++++++++ 3 files changed, 56 insertions(+) diff --git a/CHANGELOG/CHANGELOG-1.5.md b/CHANGELOG/CHANGELOG-1.5.md index efa387aa9..ac525c16a 100644 --- a/CHANGELOG/CHANGELOG-1.5.md +++ b/CHANGELOG/CHANGELOG-1.5.md @@ -6,5 +6,7 @@ - [Add support for data file size limit](https://github.com/etcd-io/bbolt/pull/929) - [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/1153)
\ No newline at end of file diff --git a/tx_check.go b/tx_check.go index 59edf3573..a1e9b33cf 100644 --- a/tx_check.go +++ b/tx_check.go @@ -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() diff --git a/tx_check_test.go b/tx_check_test.go index a0ce69a29..33be487e5 100644 --- a/tx_check_test.go +++ b/tx_check_test.go @@ -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) @@ -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 From ce40af16b32902449aa2c0dc800dccd057798dad Mon Sep 17 00:00:00 2001 From: VihasMakwana <121151420+VihasMakwana@users.noreply.github.com> Date: Wed, 25 Feb 2026 14:16:19 +0530 Subject: [PATCH 2/4] fix: panic in the main hierarchy Signed-off-by: VihasMakwana <121151420+VihasMakwana@users.noreply.github.com> --- db.go | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/db.go b/db.go index 5d3e26496..96db07b35 100644 --- a/db.go +++ b/db.go @@ -1253,13 +1253,16 @@ func (db *DB) freepages() []common.Pgid { reachable := make(map[common.Pgid]*common.Page) nofreed := make(map[common.Pgid]bool) ech := make(chan error) + go func() { - for e := range ech { - panic(fmt.Sprintf("freepages: failed to get all reachable pages (%v)", e)) - } + defer close(ech) + tx.recursivelyCheckBucket(&tx.root, reachable, nofreed, HexKVStringer(), ech) }() - tx.recursivelyCheckBucket(&tx.root, reachable, nofreed, HexKVStringer(), ech) - close(ech) + // following for loop will exit once channel is closed in the above goroutine. + // we don't need to wait explictly with a waitgroup + for e := range ech { + panic(fmt.Sprintf("freepages: failed to get all reachable pages (%v)", e)) + } // TODO: If check bucket reported any corruptions (ech) we shouldn't proceed to freeing the pages. From 49662494513cc15495117e852ceb75b69280d7a6 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Mon, 2 Mar 2026 16:18:01 +0000 Subject: [PATCH 3/4] build(deps): Bump actions/setup-go from 6.2.0 to 6.3.0 Bumps [actions/setup-go](https://github.com/actions/setup-go) from 6.2.0 to 6.3.0. - [Release notes](https://github.com/actions/setup-go/releases) - [Commits](https://github.com/actions/setup-go/compare/7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5...4b73464bb391d4059bd26b0524d20df3927bd417) --- updated-dependencies: - dependency-name: actions/setup-go dependency-version: 6.3.0 dependency-type: direct:production update-type: version-update:semver-minor ... Signed-off-by: dependabot[bot] --- .github/workflows/benchmark-template.yaml | 2 +- .github/workflows/cross-arch-template.yaml | 2 +- .github/workflows/failpoint_test.yaml | 2 +- .github/workflows/robustness_template.yaml | 2 +- .github/workflows/tests-template.yml | 2 +- .github/workflows/tests_amd64.yaml | 2 +- .github/workflows/tests_arm64.yaml | 2 +- .github/workflows/tests_windows.yml | 4 ++-- 8 files changed, 9 insertions(+), 9 deletions(-) diff --git a/.github/workflows/benchmark-template.yaml b/.github/workflows/benchmark-template.yaml index e142c84e1..8661196fe 100644 --- a/.github/workflows/benchmark-template.yaml +++ b/.github/workflows/benchmark-template.yaml @@ -26,7 +26,7 @@ jobs: fetch-depth: 0 - id: goversion run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: ${{ steps.goversion.outputs.goversion }} - name: Run Benchmarks diff --git a/.github/workflows/cross-arch-template.yaml b/.github/workflows/cross-arch-template.yaml index 0e3f5e459..ee35bb8f7 100644 --- a/.github/workflows/cross-arch-template.yaml +++ b/.github/workflows/cross-arch-template.yaml @@ -21,7 +21,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - id: goversion run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: ${{ steps.goversion.outputs.goversion }} - name: Build for ${{ inputs.os }}/${{ matrix.arch }} diff --git a/.github/workflows/failpoint_test.yaml b/.github/workflows/failpoint_test.yaml index 5e6b6ab07..5d857890e 100644 --- a/.github/workflows/failpoint_test.yaml +++ b/.github/workflows/failpoint_test.yaml @@ -12,7 +12,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - id: goversion run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: ${{ steps.goversion.outputs.goversion }} - name: Run golangci-lint diff --git a/.github/workflows/robustness_template.yaml b/.github/workflows/robustness_template.yaml index a4853a954..2f825b814 100644 --- a/.github/workflows/robustness_template.yaml +++ b/.github/workflows/robustness_template.yaml @@ -26,7 +26,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - id: goversion run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: ${{ steps.goversion.outputs.goversion }} - name: Run golangci-lint diff --git a/.github/workflows/tests-template.yml b/.github/workflows/tests-template.yml index 1a0caf27e..ddb0c8be4 100644 --- a/.github/workflows/tests-template.yml +++ b/.github/workflows/tests-template.yml @@ -24,7 +24,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - id: goversion run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: ${{ steps.goversion.outputs.goversion }} - run: make fmt diff --git a/.github/workflows/tests_amd64.yaml b/.github/workflows/tests_amd64.yaml index 570d048a1..6cb9683ab 100644 --- a/.github/workflows/tests_amd64.yaml +++ b/.github/workflows/tests_amd64.yaml @@ -20,7 +20,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - id: goversion run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: ${{ steps.goversion.outputs.goversion }} - name: Run golangci-lint diff --git a/.github/workflows/tests_arm64.yaml b/.github/workflows/tests_arm64.yaml index 3e9a0d15a..1f8bd921b 100644 --- a/.github/workflows/tests_arm64.yaml +++ b/.github/workflows/tests_arm64.yaml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - id: goversion run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: ${{ steps.goversion.outputs.goversion }} - name: Run golangci-lint diff --git a/.github/workflows/tests_windows.yml b/.github/workflows/tests_windows.yml index 2e3b872f1..877733ecf 100644 --- a/.github/workflows/tests_windows.yml +++ b/.github/workflows/tests_windows.yml @@ -22,7 +22,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - id: goversion run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: ${{ steps.goversion.outputs.goversion }} - run: make fmt @@ -51,7 +51,7 @@ jobs: - uses: actions/checkout@de0fac2e4500dabe0009e67214ff5f5447ce83dd # v6.0.2 - id: goversion run: echo "goversion=$(cat .go-version)" >> "$GITHUB_OUTPUT" - - uses: actions/setup-go@7a3fe6cf4cb3a834922a1244abfce67bcef6a0c5 # v6.2.0 + - uses: actions/setup-go@4b73464bb391d4059bd26b0524d20df3927bd417 # v6.3.0 with: go-version: ${{ steps.goversion.outputs.goversion }} - name: Run golangci-lint From 71a650f30dc778ed2eaacc531211f1061f1da6ec Mon Sep 17 00:00:00 2001 From: VihasMakwana <121151420+VihasMakwana@users.noreply.github.com> Date: Sat, 7 Mar 2026 02:33:09 +0530 Subject: [PATCH 4/4] chlog Signed-off-by: VihasMakwana <121151420+VihasMakwana@users.noreply.github.com> --- CHANGELOG/CHANGELOG-1.5.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG/CHANGELOG-1.5.md b/CHANGELOG/CHANGELOG-1.5.md index ac525c16a..8561ea8d7 100644 --- a/CHANGELOG/CHANGELOG-1.5.md +++ b/CHANGELOG/CHANGELOG-1.5.md @@ -7,6 +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/1153) +- [Recover from panics in tx.check](https://github.com/etcd-io/bbolt/pull/1160)
\ No newline at end of file