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 := (*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 {
|
||||||
// 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 @@
|
||||||
package cachingfs
|
package cachingfs
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"crypto/rand"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"io"
|
||||||
"os"
|
"os"
|
||||||
"time"
|
"time"
|
||||||
|
|
||||||
|
@ -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 {
|
||||||
// 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 {
|
||||||
|
|
||||||
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) {
|
||||||
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(
|
||||||
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 @@
|
||||||
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() {
|
||||||
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))
|
||||||
|
}
|
||||||
|
|
Loading…
Reference in New Issue