Feat: Discover automatically recovers from bad meta.json

This commit is contained in:
kayos@tcp.direct 2024-01-28 21:10:36 -08:00
parent b129c58d57
commit 374ca0d5fb
Signed by: kayos
GPG Key ID: 4B841471B4BEE979
2 changed files with 60 additions and 0 deletions

View File

@ -74,8 +74,40 @@ func (db *DB) Discover() ([]string, error) {
if _, ok := db.store[name]; ok {
continue
}
recoverOnce := &sync.Once{}
openUp:
c, e := bitcask.Open(filepath.Join(db.path, name), defaultBitcaskOptions...)
if e != nil {
retry := false
recoverOnce.Do(func() {
metaErr := new(bitcask.ErrBadMetadata)
if !errors.As(e, &metaErr) {
return
}
if !strings.Contains(metaErr.Error(), "unexpected end of JSON input") {
return
}
if c != nil {
_ = c.Close()
}
println("WARN: bitcask store", name, "has bad metadata, attempting to repair")
oldMeta := filepath.Join(db.path, name, "meta.json")
newMeta := filepath.Join(db.path, name, "meta.json.backup")
println("WARN: renaming", oldMeta, "to", newMeta)
// likely defunct lockfile is present too, remove it
if osErr := os.Rename(oldMeta, newMeta); osErr != nil {
println("WARN: failed to rename", oldMeta, "to", newMeta, ":", osErr)
return
}
if _, serr := os.Stat(filepath.Join(db.path, name, "lock")); serr == nil {
println("WARN: removing defunct lockfile")
_ = os.Remove(filepath.Join(db.path, name, "lock"))
}
retry = true
})
if retry {
goto openUp
}
errs = append(errs, e)
continue
}
@ -84,6 +116,10 @@ func (db *DB) Discover() ([]string, error) {
}
for _, e := range errs {
if err == nil {
err = e
continue
}
err = fmt.Errorf("%w: %v", err, e)
}

View File

@ -5,6 +5,7 @@ import (
"errors"
"io/fs"
"os"
"path/filepath"
"strings"
"testing"
@ -198,6 +199,29 @@ func TestDB_Init(t *testing.T) { //nolint:funlen,gocognit,cyclop
}
}
})
t.Run("RecoverBadMetaJSON", func(t *testing.T) {
var path string
path, db = newTestDB(t)
names := seedRandStores(db, t)
err := db.SyncAndCloseAll()
if err != nil {
t.Errorf("[FAIL] failed to SyncAndCloseAll: %e", err)
}
if err = os.WriteFile(filepath.Join(path, names[0], "meta.json"), []byte(""), 0644); err != nil {
t.Fatalf("[FAIL] failed to write bad meta.json: %e", err)
}
db = OpenDB(path)
_, err = db.(*DB).Discover()
if err != nil {
t.Errorf("[FAIL] failed to discover stores: %e", err)
}
if err = db.With(names[0]).Put([]byte("asdf"), []byte("asdf")); err != nil {
t.Errorf("[FAIL] failed to put value: %e", err)
}
if err = db.SyncAndCloseAll(); err != nil {
t.Errorf("[FAIL] failed to SyncAndCloseAll: %e", err)
}
})
}
func Test_Sync(t *testing.T) {