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:
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
@@ -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
|
||||||
|
|||||||
Reference in New Issue
Block a user