Skip to content

Commit

Permalink
fix: add rwlock for file to ensure thread-safe
Browse files Browse the repository at this point in the history
  • Loading branch information
rfyiamcool committed May 7, 2023
1 parent 89077fa commit 04b2095
Show file tree
Hide file tree
Showing 4 changed files with 71 additions and 44 deletions.
46 changes: 35 additions & 11 deletions file/file.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@ import (
"crypto/sha256"
"encoding/hex"
"encoding/json"
"errors"
"fmt"
"io/ioutil"
"os"
"path/filepath"
"sync"
"time"

"github.com/faabiosr/cachego"
Expand All @@ -18,6 +18,7 @@ import (
type (
file struct {
dir string
sync.RWMutex
}

fileContent struct {
Expand All @@ -30,7 +31,8 @@ const perm = 0o666

// New creates an instance of File cache
func New(dir string) cachego.Cache {
return &file{dir}
_ = os.MkdirAll(dir, os.ModePerm)
return &file{dir: dir}
}

func (f *file) createName(key string) string {
Expand All @@ -42,6 +44,9 @@ func (f *file) createName(key string) string {
}

func (f *file) read(key string) (*fileContent, error) {
f.RLock()
defer f.RUnlock()

value, err := ioutil.ReadFile(f.createName(key))
if err != nil {
return nil, err
Expand All @@ -56,22 +61,28 @@ func (f *file) read(key string) (*fileContent, error) {
return content, nil
}

if content.Duration <= time.Now().Unix() {
_ = f.Delete(key)
return nil, errors.New("cache expired")
}

return content, nil
}

// Contains checks if the cached key exists into the File storage
func (f *file) Contains(key string) bool {
_, err := f.read(key)
return err == nil
content, err := f.read(key)
if err != nil {
return false
}

if f.isExpired(content) {
_ = f.Delete(key)
return false
}
return true
}

// Delete the cached key from File storage
func (f *file) Delete(key string) error {
f.Lock()
defer f.Unlock()

_, err := os.Stat(f.createName(key))
if err != nil && os.IsNotExist(err) {
return nil
Expand All @@ -87,9 +98,18 @@ func (f *file) Fetch(key string) (string, error) {
return "", err
}

if f.isExpired(content) {
_ = f.Delete(key)
return "", cachego.ErrCacheExpired
}

return content.Data, nil
}

func (f *file) isExpired(content *fileContent) bool {
return content.Duration > 0 && content.Duration <= time.Now().Unix()
}

// FetchMulti retrieve multiple cached values from keys of the File storage
func (f *file) FetchMulti(keys []string) map[string]string {
result := make(map[string]string)
Expand All @@ -105,6 +125,9 @@ func (f *file) FetchMulti(keys []string) map[string]string {

// Flush removes all cached keys of the File storage
func (f *file) Flush() error {
f.Lock()
defer f.Unlock()

dir, err := os.Open(f.dir)
if err != nil {
return err
Expand All @@ -125,14 +148,15 @@ func (f *file) Flush() error {

// Save a value in File storage by key
func (f *file) Save(key string, value string, lifeTime time.Duration) error {
duration := int64(0)
f.Lock()
defer f.Unlock()

duration := int64(0)
if lifeTime > 0 {
duration = time.Now().Unix() + int64(lifeTime.Seconds())
}

content := &fileContent{duration, value}

data, err := json.Marshal(content)
if err != nil {
return err
Expand Down
59 changes: 26 additions & 33 deletions file/file_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,9 @@ import (
"io/ioutil"
"testing"
"time"

"github.com/faabiosr/cachego"
"github.com/stretchr/testify/assert"
)

const (
Expand All @@ -19,51 +22,41 @@ func TestFile(t *testing.T) {

c := New(dir)

if err := c.Save(testKey, testValue, 1*time.Nanosecond); err != nil {
t.Errorf("save fail: expected nil, got %v", err)
}
err = c.Save(testKey, testValue, 1*time.Nanosecond)
assert.Equal(t, nil, err)

if _, err := c.Fetch(testKey); err == nil {
t.Errorf("fetch fail: expected an error, got %v", err)
}
res, err := c.Fetch(testKey)
assert.Equal(t, "", res)
assert.Equal(t, cachego.ErrCacheExpired, err)

_ = c.Save(testKey, testValue, 10*time.Second)
err = c.Save(testKey, testValue, 10*time.Second)
assert.Equal(t, nil, err)

if res, _ := c.Fetch(testKey); res != testValue {
t.Errorf("fetch fail, wrong value: expected %s, got %s", testValue, res)
}
res, err = c.Fetch(testKey)
assert.Equal(t, nil, err)
assert.Equal(t, testValue, res)

_ = c.Save(testKey, testValue, 0)

if !c.Contains(testKey) {
t.Errorf("contains failed: the key %s should be exist", testKey)
}
assert.Equal(t, true, c.Contains(testKey))

_ = c.Save("bar", testValue, 0)

if values := c.FetchMulti([]string{testKey, "bar"}); len(values) == 0 {
t.Errorf("fetch multi failed: expected %d, got %d", 2, len(values))
}
values := c.FetchMulti([]string{testKey, "bar"})
assert.Equal(t, 2, len(values))

if err := c.Flush(); err != nil {
t.Errorf("flush failed: expected nil, got %v", err)
}
err = c.Flush()
assert.Equal(t, nil, err)

if c.Contains(testKey) {
t.Errorf("contains failed: the key %s should not be exist", testKey)
}
assert.Equal(t, false, c.Contains(testKey))

c = New("./test/")
err = c.Save(testKey, testValue, 0)
assert.Equal(t, nil, err)

if err := c.Save(testKey, testValue, 0); err == nil {
t.Errorf("save failed: expected an error, got %v", err)
}

if _, err := c.Fetch(testKey); err == nil {
t.Errorf("fetch failed: expected and error, got %v", err)
}
res, err = c.Fetch(testKey)
assert.Equal(t, testValue, res)
assert.Equal(t, nil, err)

if err := c.Flush(); err == nil {
t.Errorf("flush failed: expected an error, got %v", err)
}
err = c.Flush()
assert.Equal(t, nil, err)
}
4 changes: 4 additions & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -6,18 +6,21 @@ require (
github.com/bradfitz/gomemcache v0.0.0-20230124162541-5f7a7d875746
github.com/mattn/go-sqlite3 v1.14.16
github.com/redis/go-redis/v9 v9.0.2
github.com/stretchr/testify v1.8.1
go.etcd.io/bbolt v1.3.7
go.mongodb.org/mongo-driver v1.11.3
)

require (
github.com/cespare/xxhash/v2 v2.2.0 // indirect
github.com/davecgh/go-spew v1.1.1 // indirect
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
github.com/golang/snappy v0.0.1 // indirect
github.com/google/go-cmp v0.5.5 // indirect
github.com/klauspost/compress v1.13.6 // indirect
github.com/montanaflynn/stats v0.0.0-20171201202039-1bf9dbcd8cbe // indirect
github.com/pkg/errors v0.9.1 // indirect
github.com/pmezard/go-difflib v1.0.0 // indirect
github.com/xdg-go/pbkdf2 v1.0.0 // indirect
github.com/xdg-go/scram v1.1.1 // indirect
github.com/xdg-go/stringprep v1.0.3 // indirect
Expand All @@ -27,4 +30,5 @@ require (
golang.org/x/sys v0.4.0 // indirect
golang.org/x/text v0.3.7 // indirect
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
gopkg.in/yaml.v3 v3.0.1 // indirect
)
6 changes: 6 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -32,8 +32,13 @@ github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZN
github.com/redis/go-redis/v9 v9.0.2 h1:BA426Zqe/7r56kCcvxYLWe1mkaz71LKF77GwgFzSxfE=
github.com/redis/go-redis/v9 v9.0.2/go.mod h1:/xDTe9EF1LM61hek62Poq2nzQSGj0xSrEtEHbBQevps=
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
github.com/stretchr/objx v0.5.0/go.mod h1:Yh+to48EsGEfYuaHDzXPcE3xhTkx73EhmCGUpEOglKo=
github.com/stretchr/testify v1.6.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
github.com/stretchr/testify v1.8.1 h1:w7B6lhMri9wdJUVmEZPGGhZzrYTPvgJArz7wNPgYKsk=
github.com/stretchr/testify v1.8.1/go.mod h1:w2LPCIKwWwSfY2zedu0+kehJoqGctiVI29o6fzry7u4=
github.com/tidwall/pretty v1.0.0 h1:HsD+QiTn7sK6flMKIvNmpqz1qrpP3Ps6jOKIKMooyg4=
github.com/tidwall/pretty v1.0.0/go.mod h1:XNkn88O1ChpSDQmQeStsy+sBenx6DDtFZJxhVysOjyk=
github.com/xdg-go/pbkdf2 v1.0.0 h1:Su7DPu48wXMwC3bs7MCNG+z4FhcyEuz5dlvchbq0B0c=
Expand Down Expand Up @@ -67,6 +72,7 @@ golang.org/x/xerrors v0.0.0-20191204190536-9bdfabe68543/go.mod h1:I/5z698sn9Ka8T
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 h1:go1bK/D/BFZV2I8cIQd1NKEZ+0owSTG1fDTci4IqFcE=
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1/go.mod h1:I/5z698sn9Ka8TeJc9MKroUUfqBBauWjQqLJ2OPfmY0=
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
Expand Down

0 comments on commit 04b2095

Please sign in to comment.