Recover from bbolt panics on corrupted database

When bbolt encounters corrupted page data at runtime, it panics
instead of returning an error. Wrap all DB transactions with
recover to catch these panics, delete the corrupted database
file, and reopen a fresh one.
This commit is contained in:
世界
2026-02-06 19:35:32 +08:00
parent a2d313c59b
commit c45ea8dfac
3 changed files with 60 additions and 17 deletions

View File

@@ -45,6 +45,7 @@ type CacheFile struct {
storeRDRC bool storeRDRC bool
rdrcTimeout time.Duration rdrcTimeout time.Duration
DB *bbolt.DB DB *bbolt.DB
resetAccess sync.Mutex
saveMetadataTimer *time.Timer saveMetadataTimer *time.Timer
saveFakeIPAccess sync.RWMutex saveFakeIPAccess sync.RWMutex
saveDomain map[netip.Addr]string saveDomain map[netip.Addr]string
@@ -169,13 +170,55 @@ func (c *CacheFile) Close() error {
return c.DB.Close() return c.DB.Close()
} }
func (c *CacheFile) view(fn func(tx *bbolt.Tx) error) (err error) {
defer func() {
if r := recover(); r != nil {
c.resetDB()
err = E.New("database corrupted: ", r)
}
}()
return c.DB.View(fn)
}
func (c *CacheFile) batch(fn func(tx *bbolt.Tx) error) (err error) {
defer func() {
if r := recover(); r != nil {
c.resetDB()
err = E.New("database corrupted: ", r)
}
}()
return c.DB.Batch(fn)
}
func (c *CacheFile) update(fn func(tx *bbolt.Tx) error) (err error) {
defer func() {
if r := recover(); r != nil {
c.resetDB()
err = E.New("database corrupted: ", r)
}
}()
return c.DB.Update(fn)
}
func (c *CacheFile) resetDB() {
c.resetAccess.Lock()
defer c.resetAccess.Unlock()
c.DB.Close()
os.Remove(c.path)
db, err := bbolt.Open(c.path, 0o666, &bbolt.Options{Timeout: time.Second})
if err == nil {
_ = filemanager.Chown(c.ctx, c.path)
c.DB = db
}
}
func (c *CacheFile) StoreFakeIP() bool { func (c *CacheFile) StoreFakeIP() bool {
return c.storeFakeIP return c.storeFakeIP
} }
func (c *CacheFile) LoadMode() string { func (c *CacheFile) LoadMode() string {
var mode string var mode string
c.DB.View(func(t *bbolt.Tx) error { c.view(func(t *bbolt.Tx) error {
bucket := t.Bucket(bucketMode) bucket := t.Bucket(bucketMode)
if bucket == nil { if bucket == nil {
return nil return nil
@@ -193,7 +236,7 @@ func (c *CacheFile) LoadMode() string {
} }
func (c *CacheFile) StoreMode(mode string) error { func (c *CacheFile) StoreMode(mode string) error {
return c.DB.Batch(func(t *bbolt.Tx) error { return c.batch(func(t *bbolt.Tx) error {
bucket, err := t.CreateBucketIfNotExists(bucketMode) bucket, err := t.CreateBucketIfNotExists(bucketMode)
if err != nil { if err != nil {
return err return err
@@ -230,7 +273,7 @@ func (c *CacheFile) createBucket(t *bbolt.Tx, key []byte) (*bbolt.Bucket, error)
func (c *CacheFile) LoadSelected(group string) string { func (c *CacheFile) LoadSelected(group string) string {
var selected string var selected string
c.DB.View(func(t *bbolt.Tx) error { c.view(func(t *bbolt.Tx) error {
bucket := c.bucket(t, bucketSelected) bucket := c.bucket(t, bucketSelected)
if bucket == nil { if bucket == nil {
return nil return nil
@@ -245,7 +288,7 @@ func (c *CacheFile) LoadSelected(group string) string {
} }
func (c *CacheFile) StoreSelected(group, selected string) error { func (c *CacheFile) StoreSelected(group, selected string) error {
return c.DB.Batch(func(t *bbolt.Tx) error { return c.batch(func(t *bbolt.Tx) error {
bucket, err := c.createBucket(t, bucketSelected) bucket, err := c.createBucket(t, bucketSelected)
if err != nil { if err != nil {
return err return err
@@ -255,7 +298,7 @@ func (c *CacheFile) StoreSelected(group, selected string) error {
} }
func (c *CacheFile) LoadGroupExpand(group string) (isExpand bool, loaded bool) { func (c *CacheFile) LoadGroupExpand(group string) (isExpand bool, loaded bool) {
c.DB.View(func(t *bbolt.Tx) error { c.view(func(t *bbolt.Tx) error {
bucket := c.bucket(t, bucketExpand) bucket := c.bucket(t, bucketExpand)
if bucket == nil { if bucket == nil {
return nil return nil
@@ -271,7 +314,7 @@ func (c *CacheFile) LoadGroupExpand(group string) (isExpand bool, loaded bool) {
} }
func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error { func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error {
return c.DB.Batch(func(t *bbolt.Tx) error { return c.batch(func(t *bbolt.Tx) error {
bucket, err := c.createBucket(t, bucketExpand) bucket, err := c.createBucket(t, bucketExpand)
if err != nil { if err != nil {
return err return err
@@ -286,7 +329,7 @@ func (c *CacheFile) StoreGroupExpand(group string, isExpand bool) error {
func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary { func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary {
var savedSet adapter.SavedBinary var savedSet adapter.SavedBinary
err := c.DB.View(func(t *bbolt.Tx) error { err := c.view(func(t *bbolt.Tx) error {
bucket := c.bucket(t, bucketRuleSet) bucket := c.bucket(t, bucketRuleSet)
if bucket == nil { if bucket == nil {
return os.ErrNotExist return os.ErrNotExist
@@ -304,7 +347,7 @@ func (c *CacheFile) LoadRuleSet(tag string) *adapter.SavedBinary {
} }
func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error { func (c *CacheFile) SaveRuleSet(tag string, set *adapter.SavedBinary) error {
return c.DB.Batch(func(t *bbolt.Tx) error { return c.batch(func(t *bbolt.Tx) error {
bucket, err := c.createBucket(t, bucketRuleSet) bucket, err := c.createBucket(t, bucketRuleSet)
if err != nil { if err != nil {
return err return err

View File

@@ -23,7 +23,7 @@ var (
func (c *CacheFile) FakeIPMetadata() *adapter.FakeIPMetadata { func (c *CacheFile) FakeIPMetadata() *adapter.FakeIPMetadata {
var metadata adapter.FakeIPMetadata var metadata adapter.FakeIPMetadata
err := c.DB.Batch(func(tx *bbolt.Tx) error { err := c.batch(func(tx *bbolt.Tx) error {
bucket := tx.Bucket(bucketFakeIP) bucket := tx.Bucket(bucketFakeIP)
if bucket == nil { if bucket == nil {
return os.ErrNotExist return os.ErrNotExist
@@ -45,7 +45,7 @@ func (c *CacheFile) FakeIPMetadata() *adapter.FakeIPMetadata {
} }
func (c *CacheFile) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) error { func (c *CacheFile) FakeIPSaveMetadata(metadata *adapter.FakeIPMetadata) error {
return c.DB.Batch(func(tx *bbolt.Tx) error { return c.batch(func(tx *bbolt.Tx) error {
bucket, err := tx.CreateBucketIfNotExists(bucketFakeIP) bucket, err := tx.CreateBucketIfNotExists(bucketFakeIP)
if err != nil { if err != nil {
return err return err
@@ -69,7 +69,7 @@ func (c *CacheFile) FakeIPSaveMetadataAsync(metadata *adapter.FakeIPMetadata) {
} }
func (c *CacheFile) FakeIPStore(address netip.Addr, domain string) error { func (c *CacheFile) FakeIPStore(address netip.Addr, domain string) error {
return c.DB.Batch(func(tx *bbolt.Tx) error { return c.batch(func(tx *bbolt.Tx) error {
bucket, err := tx.CreateBucketIfNotExists(bucketFakeIP) bucket, err := tx.CreateBucketIfNotExists(bucketFakeIP)
if err != nil { if err != nil {
return err return err
@@ -136,7 +136,7 @@ func (c *CacheFile) FakeIPLoad(address netip.Addr) (string, bool) {
return cachedDomain, true return cachedDomain, true
} }
var domain string var domain string
_ = c.DB.View(func(tx *bbolt.Tx) error { _ = c.view(func(tx *bbolt.Tx) error {
bucket := tx.Bucket(bucketFakeIP) bucket := tx.Bucket(bucketFakeIP)
if bucket == nil { if bucket == nil {
return nil return nil
@@ -163,7 +163,7 @@ func (c *CacheFile) FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bo
return cachedAddress, true return cachedAddress, true
} }
var address netip.Addr var address netip.Addr
_ = c.DB.View(func(tx *bbolt.Tx) error { _ = c.view(func(tx *bbolt.Tx) error {
var bucket *bbolt.Bucket var bucket *bbolt.Bucket
if isIPv6 { if isIPv6 {
bucket = tx.Bucket(bucketFakeIPDomain6) bucket = tx.Bucket(bucketFakeIPDomain6)
@@ -180,7 +180,7 @@ func (c *CacheFile) FakeIPLoadDomain(domain string, isIPv6 bool) (netip.Addr, bo
} }
func (c *CacheFile) FakeIPReset() error { func (c *CacheFile) FakeIPReset() error {
return c.DB.Batch(func(tx *bbolt.Tx) error { return c.batch(func(tx *bbolt.Tx) error {
err := tx.DeleteBucket(bucketFakeIP) err := tx.DeleteBucket(bucketFakeIP)
if err != nil { if err != nil {
return err return err

View File

@@ -31,7 +31,7 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) (
copy(key[2:], qName) copy(key[2:], qName)
defer buf.Put(key) defer buf.Put(key)
var deleteCache bool var deleteCache bool
err := c.DB.View(func(tx *bbolt.Tx) error { err := c.view(func(tx *bbolt.Tx) error {
bucket := c.bucket(tx, bucketRDRC) bucket := c.bucket(tx, bucketRDRC)
if bucket == nil { if bucket == nil {
return nil return nil
@@ -56,7 +56,7 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) (
return return
} }
if deleteCache { if deleteCache {
c.DB.Update(func(tx *bbolt.Tx) error { c.update(func(tx *bbolt.Tx) error {
bucket := c.bucket(tx, bucketRDRC) bucket := c.bucket(tx, bucketRDRC)
if bucket == nil { if bucket == nil {
return nil return nil
@@ -72,7 +72,7 @@ func (c *CacheFile) LoadRDRC(transportName string, qName string, qType uint16) (
} }
func (c *CacheFile) SaveRDRC(transportName string, qName string, qType uint16) error { func (c *CacheFile) SaveRDRC(transportName string, qName string, qType uint16) error {
return c.DB.Batch(func(tx *bbolt.Tx) error { return c.batch(func(tx *bbolt.Tx) error {
bucket, err := c.createBucket(tx, bucketRDRC) bucket, err := c.createBucket(tx, bucketRDRC)
if err != nil { if err != nil {
return err return err