diff --git a/experimental/cachefile/cache.go b/experimental/cachefile/cache.go index 210813f1..0d714a60 100644 --- a/experimental/cachefile/cache.go +++ b/experimental/cachefile/cache.go @@ -53,7 +53,7 @@ type CacheFile struct { rdrcTimeout time.Duration optimisticTimeout time.Duration DB *bbolt.DB - resetAccess sync.Mutex + dbAccess sync.RWMutex saveMetadataAccess sync.Mutex saveMetadata *adapter.FakeIPMetadata saveMetadataTimer *time.Timer @@ -180,19 +180,29 @@ func (c *CacheFile) start() error { var db *bbolt.DB for range 10 { 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 { break } - if errors.Is(err, bboltErrors.ErrTimeout) { - continue + c.logger.Error("database corrupted: ", err, ": resetting") + 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 { return err @@ -230,46 +240,83 @@ func (c *CacheFile) start() 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 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) { + db := c.database() defer func() { - if r := recover(); r != nil { - c.resetDB() + r := recover() + if r != nil { + c.resetDB(db, 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) { + db := c.database() defer func() { - if r := recover(); r != nil { - c.resetDB() + r := recover() + if r != nil { + c.resetDB(db, 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) { + db := c.database() defer func() { - if r := recover(); r != nil { - c.resetDB() + r := recover() + if r != nil { + c.resetDB(db, r) err = E.New("database corrupted: ", r) } }() - return c.DB.Update(fn) + return db.Update(fn) } -func (c *CacheFile) resetDB() { - c.resetAccess.Lock() - defer c.resetAccess.Unlock() - c.DB.Close() +func (c *CacheFile) resetDB(failedDB *bbolt.DB, reason any) { + c.dbAccess.Lock() + defer c.dbAccess.Unlock() + if c.DB != failedDB { + return + } + c.logger.Error("database corrupted: ", reason, ": resetting") + failedDB.Close() filemanager.Remove(c.ctx, c.path) db, err := bbolt.Open(c.path, 0o666, &bbolt.Options{Timeout: time.Second}) if err == nil { diff --git a/go.mod b/go.mod index 342d9992..258fb961 100644 --- a/go.mod +++ b/go.mod @@ -34,7 +34,7 @@ require ( github.com/oschwald/maxminddb-golang v1.13.1 github.com/pkg/sftp v1.13.10 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/cronet-go v0.0.0-20260807162344-ec9a39c5ba3b github.com/sagernet/cronet-go/all v0.0.0-20260807162344-ec9a39c5ba3b diff --git a/go.sum b/go.sum index a81bcaf1..0f928003 100644 --- a/go.sum +++ b/go.sum @@ -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/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/bbolt v0.0.0-20260821040940-d7518b45c52b h1:gjt/KuFojUO9PXzkWCpkIx/3Kd1VyJ/iR2PwS7gx8js= -github.com/sagernet/bbolt v0.0.0-20260821040940-d7518b45c52b/go.mod h1:7/qFPFH5fqK5XQeY8EA1Qpaly08Av77p38Zt50ieQww= +github.com/sagernet/bbolt v0.0.0-20260823094646-e24805439c9c h1:JdEDkgiwsDxiZb4+dQYgVOU/lND+jZxFmN5y/q49EXk= +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/go.mod h1:O64VyOjjhrkLmQIjF4KGRrJO/5dVXFdpEmCW/eISRAI= github.com/sagernet/cronet-go v0.0.0-20260807162344-ec9a39c5ba3b h1:SfX10lRxbq9ixHm4OZ4/7q+TSpK4Gh0lEVrPaIAB/K0=