Commit 13eb2958 authored by Aaron Jacobs's avatar Aaron Jacobs

Added support for telling Linux not to abandon the page cache on open.

parents 9e84136e eacbdb8d
...@@ -512,6 +512,10 @@ func (c *Connection) kernelResponseForOp( ...@@ -512,6 +512,10 @@ func (c *Connection) kernelResponseForOp(
out := (*fusekernel.OpenOut)(m.Grow(unsafe.Sizeof(fusekernel.OpenOut{}))) out := (*fusekernel.OpenOut)(m.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
out.Fh = uint64(o.Handle) out.Fh = uint64(o.Handle)
if o.KeepPageCache {
out.OpenFlags |= uint32(fusekernel.OpenKeepCache)
}
case *fuseops.ReadFileOp: case *fuseops.ReadFileOp:
// convertInMessage already set up the destination buffer to be at the end // convertInMessage already set up the destination buffer to be at the end
// of the out message. We need only shrink to the right size based on how // of the out message. We need only shrink to the right size based on how
......
...@@ -446,6 +446,22 @@ type OpenFileOp struct { ...@@ -446,6 +446,22 @@ type OpenFileOp struct {
// file handle. The file system must ensure this ID remains valid until a // file handle. The file system must ensure this ID remains valid until a
// later call to ReleaseFileHandle. // later call to ReleaseFileHandle.
Handle HandleID Handle HandleID
// By default, fuse invalidates the kernel's page cache for an inode when a
// new file handle is opened for that inode (cf. https://goo.gl/2rZ9uk). The
// intent appears to be to allow users to "see" content that has changed
// remotely on a networked file system by re-opening the file.
//
// For file systems where this is not a concern because all modifications for
// a particular inode go through the kernel, set this field to true to
// disable this behavior.
//
// (More discussion: http://goo.gl/cafzWF)
//
// Note that on OS X it appears that the behavior is always as if this field
// is set to true, regardless of its value, at least for files opened in the
// same mode. (Cf. https://github.com/osxfuse/osxfuse/issues/223)
KeepPageCache bool
} }
// Read data from a file previously opened with CreateFile or OpenFile. // Read data from a file previously opened with CreateFile or OpenFile.
......
...@@ -15,7 +15,9 @@ ...@@ -15,7 +15,9 @@
package cachingfs package cachingfs
import ( import (
"crypto/rand"
"fmt" "fmt"
"io"
"os" "os"
"time" "time"
...@@ -43,6 +45,10 @@ const ( ...@@ -43,6 +45,10 @@ const (
// inode entries and attributes to be cached, used when responding to fuse // inode entries and attributes to be cached, used when responding to fuse
// requests. It also exposes methods for renumbering inodes and updating mtimes // requests. It also exposes methods for renumbering inodes and updating mtimes
// that are useful in testing that these durations are honored. // that are useful in testing that these durations are honored.
//
// Each file responds to reads with random contents. SetKeepCache can be used
// to control whether the response to OpenFileOp tells the kernel to keep the
// file's data in the page cache or not.
type CachingFS interface { type CachingFS interface {
fuseutil.FileSystem fuseutil.FileSystem
...@@ -57,6 +63,10 @@ type CachingFS interface { ...@@ -57,6 +63,10 @@ type CachingFS interface {
// Cause further queries for the attributes of inodes to use the supplied // Cause further queries for the attributes of inodes to use the supplied
// time as the inode's mtime. // time as the inode's mtime.
SetMtime(mtime time.Time) SetMtime(mtime time.Time)
// Instruct the file system whether or not to reply to OpenFileOp with
// FOPEN_KEEP_CACHE set.
SetKeepCache(keep bool)
} }
// Create a file system that issues cacheable responses according to the // Create a file system that issues cacheable responses according to the
...@@ -116,6 +126,9 @@ type cachingFS struct { ...@@ -116,6 +126,9 @@ type cachingFS struct {
mu syncutil.InvariantMutex mu syncutil.InvariantMutex
// GUARDED_BY(mu)
keepPageCache bool
// The current ID of the lowest numbered non-root inode. // The current ID of the lowest numbered non-root inode.
// //
// INVARIANT: baseID > fuseops.RootInodeID // INVARIANT: baseID > fuseops.RootInodeID
...@@ -236,6 +249,14 @@ func (fs *cachingFS) SetMtime(mtime time.Time) { ...@@ -236,6 +249,14 @@ func (fs *cachingFS) SetMtime(mtime time.Time) {
fs.mtime = mtime fs.mtime = mtime
} }
// LOCKS_EXCLUDED(fs.mu)
func (fs *cachingFS) SetKeepCache(keep bool) {
fs.mu.Lock()
defer fs.mu.Unlock()
fs.keepPageCache = keep
}
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
// FileSystem methods // FileSystem methods
//////////////////////////////////////////////////////////////////////// ////////////////////////////////////////////////////////////////////////
...@@ -335,5 +356,17 @@ func (fs *cachingFS) OpenDir( ...@@ -335,5 +356,17 @@ func (fs *cachingFS) OpenDir(
func (fs *cachingFS) OpenFile( func (fs *cachingFS) OpenFile(
ctx context.Context, ctx context.Context,
op *fuseops.OpenFileOp) (err error) { op *fuseops.OpenFileOp) (err error) {
fs.mu.Lock()
defer fs.mu.Unlock()
op.KeepPageCache = fs.keepPageCache
return
}
func (fs *cachingFS) ReadFile(
ctx context.Context,
op *fuseops.ReadFileOp) (err error) {
op.BytesRead, err = io.ReadFull(rand.Reader, op.Dst)
return return
} }
...@@ -15,6 +15,8 @@ ...@@ -15,6 +15,8 @@
package cachingfs_test package cachingfs_test
import ( import (
"bytes"
"io/ioutil"
"os" "os"
"path" "path"
"runtime" "runtime"
...@@ -531,3 +533,189 @@ func (t *AttributeCachingTest) StatRenumberMtimeStat_ViaFileDescriptor() { ...@@ -531,3 +533,189 @@ func (t *AttributeCachingTest) StatRenumberMtimeStat_ViaFileDescriptor() {
ExpectThat(dirAfter.ModTime(), timeutil.TimeEq(newMtime)) ExpectThat(dirAfter.ModTime(), timeutil.TimeEq(newMtime))
ExpectThat(barAfter.ModTime(), timeutil.TimeEq(newMtime)) ExpectThat(barAfter.ModTime(), timeutil.TimeEq(newMtime))
} }
////////////////////////////////////////////////////////////////////////
// Page cache
////////////////////////////////////////////////////////////////////////
type PageCacheTest struct {
cachingFSTest
}
var _ SetUpInterface = &PageCacheTest{}
func init() { RegisterTestSuite(&PageCacheTest{}) }
func (t *PageCacheTest) SetUp(ti *TestInfo) {
const (
lookupEntryTimeout = 0
getattrTimeout = 0
)
t.cachingFSTest.setUp(ti, lookupEntryTimeout, getattrTimeout)
}
func (t *PageCacheTest) SingleFileHandle_NoKeepCache() {
t.fs.SetKeepCache(false)
// Open the file.
f, err := os.Open(path.Join(t.Dir, "foo"))
AssertEq(nil, err)
defer f.Close()
// Read its contents once.
f.Seek(0, 0)
AssertEq(nil, err)
c1, err := ioutil.ReadAll(f)
AssertEq(nil, err)
AssertEq(cachingfs.FooSize, len(c1))
// And again.
f.Seek(0, 0)
AssertEq(nil, err)
c2, err := ioutil.ReadAll(f)
AssertEq(nil, err)
AssertEq(cachingfs.FooSize, len(c2))
// We should have seen the same contents each time.
ExpectTrue(bytes.Equal(c1, c2))
}
func (t *PageCacheTest) SingleFileHandle_KeepCache() {
t.fs.SetKeepCache(true)
// Open the file.
f, err := os.Open(path.Join(t.Dir, "foo"))
AssertEq(nil, err)
defer f.Close()
// Read its contents once.
f.Seek(0, 0)
AssertEq(nil, err)
c1, err := ioutil.ReadAll(f)
AssertEq(nil, err)
AssertEq(cachingfs.FooSize, len(c1))
// And again.
f.Seek(0, 0)
AssertEq(nil, err)
c2, err := ioutil.ReadAll(f)
AssertEq(nil, err)
AssertEq(cachingfs.FooSize, len(c2))
// We should have seen the same contents each time.
ExpectTrue(bytes.Equal(c1, c2))
}
func (t *PageCacheTest) TwoFileHandles_NoKeepCache() {
t.fs.SetKeepCache(false)
// SetKeepCache(false) doesn't work on OS X. See the notes on
// OpenFileOp.KeepPageCache.
if runtime.GOOS == "darwin" {
return
}
// Open the file.
f1, err := os.Open(path.Join(t.Dir, "foo"))
AssertEq(nil, err)
defer f1.Close()
// Read its contents once.
f1.Seek(0, 0)
AssertEq(nil, err)
c1, err := ioutil.ReadAll(f1)
AssertEq(nil, err)
AssertEq(cachingfs.FooSize, len(c1))
// Open a second handle.
f2, err := os.Open(path.Join(t.Dir, "foo"))
AssertEq(nil, err)
defer f2.Close()
// We should see different contents if we read from that handle, due to the
// cache being invalidated at the time of opening.
f2.Seek(0, 0)
AssertEq(nil, err)
c2, err := ioutil.ReadAll(f2)
AssertEq(nil, err)
AssertEq(cachingfs.FooSize, len(c2))
ExpectFalse(bytes.Equal(c1, c2))
// Another read from the second handle should give the same result as the
// first one from that handle.
f2.Seek(0, 0)
AssertEq(nil, err)
c3, err := ioutil.ReadAll(f2)
AssertEq(nil, err)
AssertEq(cachingfs.FooSize, len(c3))
ExpectTrue(bytes.Equal(c2, c3))
// And another read from the first handle should give the same result yet
// again.
f1.Seek(0, 0)
AssertEq(nil, err)
c4, err := ioutil.ReadAll(f1)
AssertEq(nil, err)
AssertEq(cachingfs.FooSize, len(c4))
ExpectTrue(bytes.Equal(c2, c4))
}
func (t *PageCacheTest) TwoFileHandles_KeepCache() {
t.fs.SetKeepCache(true)
// Open the file.
f1, err := os.Open(path.Join(t.Dir, "foo"))
AssertEq(nil, err)
defer f1.Close()
// Read its contents once.
f1.Seek(0, 0)
AssertEq(nil, err)
c1, err := ioutil.ReadAll(f1)
AssertEq(nil, err)
AssertEq(cachingfs.FooSize, len(c1))
// Open a second handle.
f2, err := os.Open(path.Join(t.Dir, "foo"))
AssertEq(nil, err)
defer f2.Close()
// We should see the same contents when we read via the second handle.
f2.Seek(0, 0)
AssertEq(nil, err)
c2, err := ioutil.ReadAll(f2)
AssertEq(nil, err)
AssertEq(cachingfs.FooSize, len(c2))
ExpectTrue(bytes.Equal(c1, c2))
// Ditto if we read again from the first.
f1.Seek(0, 0)
AssertEq(nil, err)
c3, err := ioutil.ReadAll(f1)
AssertEq(nil, err)
AssertEq(cachingfs.FooSize, len(c3))
ExpectTrue(bytes.Equal(c1, c3))
}
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment