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