Fix bbolt recover

This commit is contained in:
世界
2026-08-30 17:41:46 +08:00
parent 82098a8760
commit 080f1c56aa
3 changed files with 75 additions and 28 deletions
+72 -25
View File
@@ -53,7 +53,7 @@ type CacheFile struct {
rdrcTimeout time.Duration rdrcTimeout time.Duration
optimisticTimeout time.Duration optimisticTimeout time.Duration
DB *bbolt.DB DB *bbolt.DB
resetAccess sync.Mutex dbAccess sync.RWMutex
saveMetadataAccess sync.Mutex saveMetadataAccess sync.Mutex
saveMetadata *adapter.FakeIPMetadata saveMetadata *adapter.FakeIPMetadata
saveMetadataTimer *time.Timer saveMetadataTimer *time.Timer
@@ -180,19 +180,29 @@ func (c *CacheFile) start() error {
var db *bbolt.DB var db *bbolt.DB
for range 10 { for range 10 {
db, err = bbolt.Open(c.path, fileMode, &options) db, err = bbolt.Open(c.path, fileMode, &options)
if err != nil {
if errors.Is(err, bboltErrors.ErrTimeout) {
continue
}
if E.IsMulti(err, bboltErrors.ErrInvalid, bboltErrors.ErrChecksum, bboltErrors.ErrVersionMismatch) {
rmErr := filemanager.Remove(c.ctx, c.path)
if rmErr != nil {
return err
}
}
time.Sleep(100 * time.Millisecond)
continue
}
err = checkDatabase(db)
if err == nil { if err == nil {
break break
} }
if errors.Is(err, bboltErrors.ErrTimeout) { c.logger.Error("database corrupted: ", err, ": resetting")
continue db.Close()
rmErr := filemanager.Remove(c.ctx, c.path)
if rmErr != nil {
return err
} }
if E.IsMulti(err, bboltErrors.ErrInvalid, bboltErrors.ErrChecksum, bboltErrors.ErrVersionMismatch) {
rmErr := filemanager.Remove(c.ctx, c.path)
if rmErr != nil {
return err
}
}
time.Sleep(100 * time.Millisecond)
} }
if err != nil { if err != nil {
return err return err
@@ -230,46 +240,83 @@ func (c *CacheFile) start() error {
} }
func (c *CacheFile) Close() error { func (c *CacheFile) Close() error {
if c.DB == nil { c.dbAccess.RLock()
db := c.DB
c.dbAccess.RUnlock()
if db == nil {
return nil return nil
} }
return c.DB.Close() return db.Close()
}
func checkDatabase(db *bbolt.DB) error {
return db.View(func(tx *bbolt.Tx) error {
var checkErr error
for txErr := range tx.Check() {
if checkErr == nil {
checkErr = txErr
}
}
return checkErr
})
}
func (c *CacheFile) database() *bbolt.DB {
c.dbAccess.RLock()
defer c.dbAccess.RUnlock()
return c.DB
} }
func (c *CacheFile) view(fn func(tx *bbolt.Tx) error) (err error) { func (c *CacheFile) view(fn func(tx *bbolt.Tx) error) (err error) {
db := c.database()
defer func() { defer func() {
if r := recover(); r != nil { r := recover()
c.resetDB() if r != nil {
c.resetDB(db, r)
err = E.New("database corrupted: ", r) err = E.New("database corrupted: ", r)
} }
}() }()
return c.DB.View(fn) return db.View(fn)
} }
func (c *CacheFile) batch(fn func(tx *bbolt.Tx) error) (err error) { func (c *CacheFile) batch(fn func(tx *bbolt.Tx) error) (err error) {
db := c.database()
defer func() { defer func() {
if r := recover(); r != nil { r := recover()
c.resetDB() if r != nil {
c.resetDB(db, r)
err = E.New("database corrupted: ", r) err = E.New("database corrupted: ", r)
} }
}() }()
return c.DB.Batch(fn) err = db.Batch(fn)
var panicErr bbolt.PanickedError
if errors.As(err, &panicErr) {
c.resetDB(db, panicErr.Reason)
return E.New("database corrupted: ", panicErr.Reason)
}
return err
} }
func (c *CacheFile) update(fn func(tx *bbolt.Tx) error) (err error) { func (c *CacheFile) update(fn func(tx *bbolt.Tx) error) (err error) {
db := c.database()
defer func() { defer func() {
if r := recover(); r != nil { r := recover()
c.resetDB() if r != nil {
c.resetDB(db, r)
err = E.New("database corrupted: ", r) err = E.New("database corrupted: ", r)
} }
}() }()
return c.DB.Update(fn) return db.Update(fn)
} }
func (c *CacheFile) resetDB() { func (c *CacheFile) resetDB(failedDB *bbolt.DB, reason any) {
c.resetAccess.Lock() c.dbAccess.Lock()
defer c.resetAccess.Unlock() defer c.dbAccess.Unlock()
c.DB.Close() if c.DB != failedDB {
return
}
c.logger.Error("database corrupted: ", reason, ": resetting")
failedDB.Close()
filemanager.Remove(c.ctx, c.path) filemanager.Remove(c.ctx, c.path)
db, err := bbolt.Open(c.path, 0o666, &bbolt.Options{Timeout: time.Second}) db, err := bbolt.Open(c.path, 0o666, &bbolt.Options{Timeout: time.Second})
if err == nil { if err == nil {
+1 -1
View File
@@ -34,7 +34,7 @@ require (
github.com/oschwald/maxminddb-golang v1.13.1 github.com/oschwald/maxminddb-golang v1.13.1
github.com/pkg/sftp v1.13.10 github.com/pkg/sftp v1.13.10
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1
github.com/sagernet/bbolt v0.0.0-20260821040940-d7518b45c52b github.com/sagernet/bbolt v0.0.0-20260823094646-e24805439c9c
github.com/sagernet/cors v1.2.1 github.com/sagernet/cors v1.2.1
github.com/sagernet/cronet-go v0.0.0-20260807162344-ec9a39c5ba3b github.com/sagernet/cronet-go v0.0.0-20260807162344-ec9a39c5ba3b
github.com/sagernet/cronet-go/all v0.0.0-20260807162344-ec9a39c5ba3b github.com/sagernet/cronet-go/all v0.0.0-20260807162344-ec9a39c5ba3b
+2 -2
View File
@@ -238,8 +238,8 @@ github.com/safchain/ethtool v0.3.0 h1:gimQJpsI6sc1yIqP/y8GYgiXn/NjgvpM0RNoWLVVmP
github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs= github.com/safchain/ethtool v0.3.0/go.mod h1:SA9BwrgyAqNo7M+uaL6IYbxpm5wk3L7Mm6ocLW+CJUs=
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 h1:qi+ijeREa0yfAaO+NOcZ81gv4uzOfALUIdhkiIFvmG4= github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1 h1:qi+ijeREa0yfAaO+NOcZ81gv4uzOfALUIdhkiIFvmG4=
github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU= github.com/sagernet/asc-go v0.0.0-20241217030726-d563060fe4e1/go.mod h1:JULDuzTMn2gyZFcjpTVZP4/UuwAdbHJ0bum2RdjXojU=
github.com/sagernet/bbolt v0.0.0-20260821040940-d7518b45c52b h1:gjt/KuFojUO9PXzkWCpkIx/3Kd1VyJ/iR2PwS7gx8js= github.com/sagernet/bbolt v0.0.0-20260823094646-e24805439c9c h1:JdEDkgiwsDxiZb4+dQYgVOU/lND+jZxFmN5y/q49EXk=
github.com/sagernet/bbolt v0.0.0-20260821040940-d7518b45c52b/go.mod h1:7/qFPFH5fqK5XQeY8EA1Qpaly08Av77p38Zt50ieQww= github.com/sagernet/bbolt v0.0.0-20260823094646-e24805439c9c/go.mod h1:7/qFPFH5fqK5XQeY8EA1Qpaly08Av77p38Zt50ieQww=
github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ= github.com/sagernet/cors v1.2.1 h1:Cv5Z8y9YSD6Gm+qSpNrL3LO4lD3eQVvbFYJSG7JCMHQ=
github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/cors v1.2.1/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI=
github.com/sagernet/cronet-go v0.0.0-20260807162344-ec9a39c5ba3b h1:SfX10lRxbq9ixHm4OZ4/7q+TSpK4Gh0lEVrPaIAB/K0= github.com/sagernet/cronet-go v0.0.0-20260807162344-ec9a39c5ba3b h1:SfX10lRxbq9ixHm4OZ4/7q+TSpK4Gh0lEVrPaIAB/K0=