Skip to content
Permalink
Browse files

cache/filecache: Recover from file corruption

Fixes #6401
  • Loading branch information...
bep committed Oct 21, 2019
1 parent 4b286b9 commit 180195aa342777fece1b29a08ec89456d7996c61
Showing with 61 additions and 1 deletion.
  1. +11 −1 cache/filecache/filecache.go
  2. +50 −0 cache/filecache/filecache_test.go
@@ -15,6 +15,7 @@ package filecache

import (
"bytes"
"errors"
"io"
"io/ioutil"
"os"
@@ -31,6 +32,9 @@ import (
"github.com/spf13/afero"
)

// ErrFatal can be used to signal an unrecoverable error.
var ErrFatal = errors.New("fatal filecache error")

const (
filecacheRootDirname = "filecache"
)
@@ -137,7 +141,13 @@ func (c *Cache) ReadOrCreate(id string,
if r := c.getOrRemove(id); r != nil {
err = read(info, r)
defer r.Close()
return
if err == nil || err == ErrFatal {
// See https://github.com/gohugoio/hugo/issues/6401
// To recover from file corruption we handle read errors
// as the cache item was not found.
// Any file permission issue will also fail in the next step.
return
}
}

f, err := helpers.OpenFileForWriting(c.Fs, id)
@@ -14,6 +14,7 @@
package filecache

import (
"errors"
"fmt"
"io"
"io/ioutil"
@@ -243,6 +244,55 @@ dir = "/cache/c"
wg.Wait()
}

func TestFileCacheReadOrCreateErrorInRead(t *testing.T) {
t.Parallel()
c := qt.New(t)

var result string

rf := func(failLevel int) func(info ItemInfo, r io.Reader) error {

return func(info ItemInfo, r io.Reader) error {
if failLevel > 0 {
if failLevel > 1 {
return ErrFatal
}
return errors.New("fail")
}

b, _ := ioutil.ReadAll(r)
result = string(b)

return nil
}
}

bf := func(s string) func(info ItemInfo, w io.WriteCloser) error {
return func(info ItemInfo, w io.WriteCloser) error {
defer w.Close()
result = s
_, err := w.Write([]byte(s))
return err
}
}

cache := NewCache(afero.NewMemMapFs(), 100*time.Hour, "")

const id = "a32"

_, err := cache.ReadOrCreate(id, rf(0), bf("v1"))
c.Assert(err, qt.IsNil)
c.Assert(result, qt.Equals, "v1")
_, err = cache.ReadOrCreate(id, rf(0), bf("v2"))
c.Assert(err, qt.IsNil)
c.Assert(result, qt.Equals, "v1")
_, err = cache.ReadOrCreate(id, rf(1), bf("v3"))
c.Assert(err, qt.IsNil)
c.Assert(result, qt.Equals, "v3")
_, err = cache.ReadOrCreate(id, rf(2), bf("v3"))
c.Assert(err, qt.Equals, ErrFatal)
}

func TestCleanID(t *testing.T) {
c := qt.New(t)
c.Assert(cleanID(filepath.FromSlash("/a/b//c.txt")), qt.Equals, filepath.FromSlash("a/b/c.txt"))

0 comments on commit 180195a

Please sign in to comment.
You can’t perform that action at this time.