1
0
mirror of https://github.com/taigrr/bitcask synced 2025-01-18 04:03:17 -08:00

Auto recovery (#153)

* implement autorepair

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* fix misspell

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

* Update internal/data/recover.go

Co-authored-by: James Mills <prologic@shortcircuit.net.au>

* Update internal/utils.go

Co-authored-by: James Mills <prologic@shortcircuit.net.au>

* Update internal/data/recover.go

Co-authored-by: James Mills <prologic@shortcircuit.net.au>

* skip failing test on windows

Signed-off-by: Ignacio Hagopian <jsign.uy@gmail.com>

Co-authored-by: James Mills <prologic@shortcircuit.net.au>
This commit is contained in:
Ignacio Hagopian
2020-05-07 14:48:36 -03:00
committed by GitHub
parent 7b24d87695
commit 8dca9cd2a7
6 changed files with 191 additions and 3 deletions

View File

@@ -12,6 +12,7 @@ type Config struct {
MaxKeySize uint32 `json:"max_key_size"`
MaxValueSize uint64 `json:"max_value_size"`
Sync bool `json:"sync"`
AutoRecovery bool `json:"autorecovery"`
}
// Load loads a configuration from the given path

89
internal/data/recover.go Normal file
View File

@@ -0,0 +1,89 @@
package data
import (
"fmt"
"io"
"os"
"path/filepath"
"github.com/prologic/bitcask/internal"
"github.com/prologic/bitcask/internal/config"
"github.com/prologic/bitcask/internal/data/codec"
)
// CheckAndRecover checks and recovers the last datafile.
// If the datafile isn't corrupted, this is a noop. If it is,
// the longest non-corrupted prefix will be kept and the rest
// will be *deleted*. Also, the index file is also *deleted* which
// will be automatically recreated on next startup.
func CheckAndRecover(path string, cfg *config.Config) error {
dfs, err := internal.GetDatafiles(path)
if err != nil {
return fmt.Errorf("scanning datafiles: %s", err)
}
if len(dfs) == 0 {
return nil
}
f := dfs[len(dfs)-1]
recovered, err := recoverDatafile(f, cfg)
if err != nil {
return fmt.Errorf("recovering data file")
}
if recovered {
if err := os.Remove(filepath.Join(path, "index")); err != nil {
return fmt.Errorf("error deleting the index on recovery: %s", err)
}
}
return nil
}
func recoverDatafile(path string, cfg *config.Config) (recovered bool, err error) {
f, err := os.Open(path)
if err != nil {
return false, fmt.Errorf("opening the datafile: %s", err)
}
defer func() {
err = f.Close()
}()
_, file := filepath.Split(path)
rPath := fmt.Sprintf("%s.recovered", file)
fr, err := os.OpenFile(rPath, os.O_CREATE|os.O_WRONLY, os.ModePerm)
if err != nil {
return false, fmt.Errorf("creating the recovered datafile: %w", err)
}
defer func() {
err = fr.Close()
}()
dec := codec.NewDecoder(f, cfg.MaxKeySize, cfg.MaxValueSize)
enc := codec.NewEncoder(fr)
e := internal.Entry{}
corrupted := false
for !corrupted {
_, err = dec.Decode(&e)
if err == io.EOF {
break
}
if codec.IsCorruptedData(err) {
corrupted = true
continue
}
if err != nil {
return false, fmt.Errorf("unexpected error while reading datafile: %w", err)
}
if _, err := enc.Encode(e); err != nil {
return false, fmt.Errorf("writing to recovered datafile: %w", err)
}
}
if !corrupted {
if err := os.Remove(fr.Name()); err != nil {
return false, fmt.Errorf("can't remove temporal recovered datafile: %w", err)
}
return false, nil
}
if err := os.Rename(rPath, path); err != nil {
return false, fmt.Errorf("removing corrupted file: %s", err)
}
return true, nil
}

View File

@@ -33,7 +33,8 @@ func DirSize(path string) (int64, error) {
// GetDatafiles returns a list of all data files stored in the database path
// given by `path`. All datafiles are identified by the the glob `*.data` and
// the basename is represented by an monotomic increasing integer.
// the basename is represented by a monotonic increasing integer.
// The returned files are *sorted* in increasing order.
func GetDatafiles(path string) ([]string, error) {
fns, err := filepath.Glob(fmt.Sprintf("%s/*.data", path))
if err != nil {