Added support for telling Linux not to abandon the page cache on open.
commit
13eb2958d3
|
@ -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
|
||||
|
|
|
@ -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.
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
|
|
@ -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))
|
||||
}
|
||||
|
|
Loading…
Reference in New Issue