diff --git a/cmd/xl-v1-object.go b/cmd/xl-v1-object.go index 08d9edb85..96187a9bb 100644 --- a/cmd/xl-v1-object.go +++ b/cmd/xl-v1-object.go @@ -121,7 +121,7 @@ func (xl xlObjects) GetObject(bucket, object string, startOffset int64, length i if xlMeta.Stat.Size > 0 && xl.objCacheEnabled { // Validate if we have previous cache. var cachedBuffer io.ReadSeeker - cachedBuffer, err = xl.objCache.Open(path.Join(bucket, object)) + cachedBuffer, err = xl.objCache.Open(path.Join(bucket, object), modTime) if err == nil { // Cache hit. // Advance the buffer to offset as if it was read. if _, err = cachedBuffer.Seek(startOffset, 0); err != nil { // Seek to the offset. diff --git a/pkg/objcache/objcache.go b/pkg/objcache/objcache.go index 7a3473338..4e266d02a 100644 --- a/pkg/objcache/objcache.go +++ b/pkg/objcache/objcache.go @@ -163,7 +163,9 @@ func (c *Cache) Create(key string, size int64) (w io.WriteCloser, err error) { // Open - open the in-memory file, returns an in memory read seeker. // returns an error ErrNotFoundInCache, if the key does not exist. -func (c *Cache) Open(key string) (io.ReadSeeker, error) { +// Returns ErrKeyNotFoundInCache if entry's lastAccessedTime is older +// than objModTime. +func (c *Cache) Open(key string, objModTime time.Time) (io.ReadSeeker, error) { // Entry exists, return the readable buffer. c.mutex.Lock() defer c.mutex.Unlock() @@ -171,6 +173,11 @@ func (c *Cache) Open(key string) (io.ReadSeeker, error) { if !ok { return nil, ErrKeyNotFoundInCache } + // Check if buf is recent copy of the object on disk. + if buf.lastAccessed.Before(objModTime) { + c.delete(key) + return nil, ErrKeyNotFoundInCache + } buf.lastAccessed = time.Now().UTC() return bytes.NewReader(buf.value), nil } diff --git a/pkg/objcache/objcache_test.go b/pkg/objcache/objcache_test.go index f24a62a9a..31d96eab7 100644 --- a/pkg/objcache/objcache_test.go +++ b/pkg/objcache/objcache_test.go @@ -57,7 +57,9 @@ func TestObjExpiry(t *testing.T) { } // Wait for 500 millisecond. time.Sleep(500 * time.Millisecond) - _, err = cache.Open("test") + // Setting objModTime to the beginning of golang's time.Time to avoid deletion of stale entry. + fakeObjModTime := time.Time{} + _, err = cache.Open("test", fakeObjModTime) if err != testCase.err { t.Errorf("Test case 1 expected %s, got instead %s", testCase.err, err) } @@ -65,6 +67,9 @@ func TestObjExpiry(t *testing.T) { // TestObjCache - tests various cases for object cache behavior. func TestObjCache(t *testing.T) { + // Setting objModTime to the beginning of golang's time.Time to avoid deletion of stale entry. + fakeObjModTime := time.Time{} + // Non exhaustive list of all object cache behavior cases. testCases := []struct { expiry time.Duration @@ -117,7 +122,7 @@ func TestObjCache(t *testing.T) { // Test 1 validating Open failure. testCase := testCases[0] cache := New(testCase.cacheSize, testCase.expiry) - _, err := cache.Open("test") + _, err := cache.Open("test", fakeObjModTime) if testCase.err != err { t.Errorf("Test case 2 expected to pass, failed instead %s", err) } @@ -157,7 +162,7 @@ func TestObjCache(t *testing.T) { if err = w.Close(); err != nil { t.Errorf("Test case 4 expected to pass, failed instead %s", err) } - r, err := cache.Open("test") + r, err := cache.Open("test", fakeObjModTime) if err != nil { t.Errorf("Test case 4 expected to pass, failed instead %s", err) } @@ -186,7 +191,7 @@ func TestObjCache(t *testing.T) { } // Delete the cache entry. cache.Delete("test") - _, err = cache.Open("test") + _, err = cache.Open("test", fakeObjModTime) if testCase.err != err { t.Errorf("Test case 5 expected to pass, failed instead %s", err) } @@ -237,3 +242,23 @@ func TestObjCache(t *testing.T) { t.Errorf("Test case 7 expected to fail, passed instead") } } + +// TestStateEntryPurge - tests if objCache purges stale entry and returns ErrKeyNotFoundInCache. +func TestStaleEntryPurge(t *testing.T) { + cache := New(1024, NoExpiry) + w, err := cache.Create("test", 5) + if err != nil { + t.Errorf("Test case expected to pass, failed instead %s", err) + } + // Write '5' bytes. + w.Write([]byte("Hello")) + // Close to successfully save into cache. + if err = w.Close(); err != nil { + t.Errorf("Test case expected to pass, failed instead %s", err) + } + + _, err = cache.Open("test", time.Now().AddDate(0, 0, 1).UTC()) + if err != ErrKeyNotFoundInCache { + t.Errorf("Test case expected to return ErrKeyNotFoundInCache, instead returned %s", err) + } +}