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

geesefs-0-30-9
Aaron Jacobs 2015-07-29 16:25:39 +10:00
commit 13eb2958d3
4 changed files with 241 additions and 0 deletions

View File

@ -512,6 +512,10 @@ func (c *Connection) kernelResponseForOp(
out := (*fusekernel.OpenOut)(m.Grow(unsafe.Sizeof(fusekernel.OpenOut{})))
out.Fh = uint64(o.Handle)
if o.KeepPageCache {
out.OpenFlags |= uint32(fusekernel.OpenKeepCache)
}
case *fuseops.ReadFileOp:
// 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

View File

@ -446,6 +446,22 @@ type OpenFileOp struct {
// file handle. The file system must ensure this ID remains valid until a
// later call to ReleaseFileHandle.
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.

View File

@ -15,7 +15,9 @@
package cachingfs
import (
"crypto/rand"
"fmt"
"io"
"os"
"time"
@ -43,6 +45,10 @@ const (
// inode entries and attributes to be cached, used when responding to fuse
// requests. It also exposes methods for renumbering inodes and updating mtimes
// 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 {
fuseutil.FileSystem
@ -57,6 +63,10 @@ type CachingFS interface {
// Cause further queries for the attributes of inodes to use the supplied
// time as the inode's mtime.
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
@ -116,6 +126,9 @@ type cachingFS struct {
mu syncutil.InvariantMutex
// GUARDED_BY(mu)
keepPageCache bool
// The current ID of the lowest numbered non-root inode.
//
// INVARIANT: baseID > fuseops.RootInodeID
@ -236,6 +249,14 @@ func (fs *cachingFS) SetMtime(mtime time.Time) {
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
////////////////////////////////////////////////////////////////////////
@ -335,5 +356,17 @@ func (fs *cachingFS) OpenDir(
func (fs *cachingFS) OpenFile(
ctx context.Context,
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
}

View File

@ -15,6 +15,8 @@
package cachingfs_test
import (
"bytes"
"io/ioutil"
"os"
"path"
"runtime"
@ -531,3 +533,189 @@ func (t *AttributeCachingTest) StatRenumberMtimeStat_ViaFileDescriptor() {
ExpectThat(dirAfter.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))
}