From b5be88054f114ed068f2d3fe72f8f9b796cb2bdc Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Wed, 29 Jul 2015 15:32:49 +1000 Subject: [PATCH 01/11] Defined SetKeepCache and read behavior. --- samples/cachingfs/caching_fs.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/samples/cachingfs/caching_fs.go b/samples/cachingfs/caching_fs.go index 2565719..fa537d2 100644 --- a/samples/cachingfs/caching_fs.go +++ b/samples/cachingfs/caching_fs.go @@ -43,6 +43,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 @@ -236,6 +240,14 @@ func (fs *cachingFS) SetMtime(mtime time.Time) { fs.mtime = mtime } +// Instruct the file system whether or not to reply to OpenFileOp with +// FOPEN_KEEP_CACHE set. +// +// LOCKS_EXCLUDED(fs.mu) +func (fs *cachingFS) SetKeepCache(keep bool) { + // TODO +} + //////////////////////////////////////////////////////////////////////// // FileSystem methods //////////////////////////////////////////////////////////////////////// From 28ee2de1e9528173b88bab6e138a9fc9750b3fea Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Wed, 29 Jul 2015 05:37:55 +0000 Subject: [PATCH 02/11] Added test names. --- samples/cachingfs/caching_fs_test.go | 37 ++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/samples/cachingfs/caching_fs_test.go b/samples/cachingfs/caching_fs_test.go index ea9f306..e467b81 100644 --- a/samples/cachingfs/caching_fs_test.go +++ b/samples/cachingfs/caching_fs_test.go @@ -531,3 +531,40 @@ 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) SingleFile_NoKeepCache() { + AssertTrue(false, "TODO") +} + +func (t *PageCacheTest) SingleFile_KeepCache() { + AssertTrue(false, "TODO") +} + +func (t *PageCacheTest) TwoFiles_NoKeepCache() { + AssertTrue(false, "TODO") +} + +func (t *PageCacheTest) TwoFiles_KeepCache() { + AssertTrue(false, "TODO") +} From a95b1fb276e7d7e96acbeab8779846db0ddf4fd3 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Wed, 29 Jul 2015 05:41:31 +0000 Subject: [PATCH 03/11] PageCacheTest.SingleFileHandle_NoKeepCache --- samples/cachingfs/caching_fs.go | 7 +++-- samples/cachingfs/caching_fs_test.go | 41 +++++++++++++++++++++++----- 2 files changed, 38 insertions(+), 10 deletions(-) diff --git a/samples/cachingfs/caching_fs.go b/samples/cachingfs/caching_fs.go index fa537d2..b03c346 100644 --- a/samples/cachingfs/caching_fs.go +++ b/samples/cachingfs/caching_fs.go @@ -61,6 +61,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 @@ -240,9 +244,6 @@ func (fs *cachingFS) SetMtime(mtime time.Time) { fs.mtime = mtime } -// Instruct the file system whether or not to reply to OpenFileOp with -// FOPEN_KEEP_CACHE set. -// // LOCKS_EXCLUDED(fs.mu) func (fs *cachingFS) SetKeepCache(keep bool) { // TODO diff --git a/samples/cachingfs/caching_fs_test.go b/samples/cachingfs/caching_fs_test.go index e467b81..634a6ef 100644 --- a/samples/cachingfs/caching_fs_test.go +++ b/samples/cachingfs/caching_fs_test.go @@ -15,6 +15,8 @@ package cachingfs_test import ( + "bytes" + "io/ioutil" "os" "path" "runtime" @@ -553,18 +555,43 @@ func (t *PageCacheTest) SetUp(ti *TestInfo) { t.cachingFSTest.setUp(ti, lookupEntryTimeout, getattrTimeout) } -func (t *PageCacheTest) SingleFile_NoKeepCache() { +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() { AssertTrue(false, "TODO") } -func (t *PageCacheTest) SingleFile_KeepCache() { +func (t *PageCacheTest) TwoFileHandles_NoKeepCache() { AssertTrue(false, "TODO") } -func (t *PageCacheTest) TwoFiles_NoKeepCache() { - AssertTrue(false, "TODO") -} - -func (t *PageCacheTest) TwoFiles_KeepCache() { +func (t *PageCacheTest) TwoFileHandles_KeepCache() { AssertTrue(false, "TODO") } From 745498b1e953e642c587d79ea0cb64bfc4b53e35 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Wed, 29 Jul 2015 05:43:23 +0000 Subject: [PATCH 04/11] cachingFS.ReadFile --- samples/cachingfs/caching_fs.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/samples/cachingfs/caching_fs.go b/samples/cachingfs/caching_fs.go index b03c346..b34a26f 100644 --- a/samples/cachingfs/caching_fs.go +++ b/samples/cachingfs/caching_fs.go @@ -15,7 +15,9 @@ package cachingfs import ( + "crypto/rand" "fmt" + "io" "os" "time" @@ -350,3 +352,10 @@ func (fs *cachingFS) OpenFile( op *fuseops.OpenFileOp) (err error) { return } + +func (fs *cachingFS) ReadFile( + ctx context.Context, + op *fuseops.ReadFileOp) (err error) { + op.BytesRead, err = io.ReadFull(rand.Reader, op.Dst) + return +} From 93388be731a5c91e2a80373e4b71132c60d889ce Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Wed, 29 Jul 2015 05:43:47 +0000 Subject: [PATCH 05/11] PageCacheTest.SingleFileHandle_KeepCache --- samples/cachingfs/caching_fs_test.go | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/samples/cachingfs/caching_fs_test.go b/samples/cachingfs/caching_fs_test.go index 634a6ef..be7ead2 100644 --- a/samples/cachingfs/caching_fs_test.go +++ b/samples/cachingfs/caching_fs_test.go @@ -585,7 +585,32 @@ func (t *PageCacheTest) SingleFileHandle_NoKeepCache() { } func (t *PageCacheTest) SingleFileHandle_KeepCache() { - AssertTrue(false, "TODO") + 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() { From 454f3959f78a5f9fc210c28a786d144a983a19cb Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Wed, 29 Jul 2015 05:46:13 +0000 Subject: [PATCH 06/11] PageCacheTest.TwoFileHandles_NoKeepCache --- samples/cachingfs/caching_fs_test.go | 55 +++++++++++++++++++++++++++- 1 file changed, 54 insertions(+), 1 deletion(-) diff --git a/samples/cachingfs/caching_fs_test.go b/samples/cachingfs/caching_fs_test.go index be7ead2..164080d 100644 --- a/samples/cachingfs/caching_fs_test.go +++ b/samples/cachingfs/caching_fs_test.go @@ -614,7 +614,60 @@ func (t *PageCacheTest) SingleFileHandle_KeepCache() { } func (t *PageCacheTest) TwoFileHandles_NoKeepCache() { - AssertTrue(false, "TODO") + t.fs.SetKeepCache(false) + + // 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() { From 04dad40a080f4f6a74088028fa295d6187bed642 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Wed, 29 Jul 2015 05:47:18 +0000 Subject: [PATCH 07/11] PageCacheTest.TwoFileHandles_KeepCache --- samples/cachingfs/caching_fs_test.go | 42 +++++++++++++++++++++++++++- 1 file changed, 41 insertions(+), 1 deletion(-) diff --git a/samples/cachingfs/caching_fs_test.go b/samples/cachingfs/caching_fs_test.go index 164080d..5634034 100644 --- a/samples/cachingfs/caching_fs_test.go +++ b/samples/cachingfs/caching_fs_test.go @@ -671,5 +671,45 @@ func (t *PageCacheTest) TwoFileHandles_NoKeepCache() { } func (t *PageCacheTest) TwoFileHandles_KeepCache() { - AssertTrue(false, "TODO") + 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)) } From 7fae3a996bea9f1eabe32eb65037bdf471079aa4 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Wed, 29 Jul 2015 05:51:20 +0000 Subject: [PATCH 08/11] Added OpenFileOp.KeepPageCache. --- fuseops/ops.go | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/fuseops/ops.go b/fuseops/ops.go index 6e6b412..8f67841 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -446,6 +446,18 @@ 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 + KeepPageCache bool } // Read data from a file previously opened with CreateFile or OpenFile. From c3c029c5c16f2f93ba22d221d3aeaeb9be27d508 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Wed, 29 Jul 2015 05:52:25 +0000 Subject: [PATCH 09/11] cachingFS.SetKeepCache --- samples/cachingfs/caching_fs.go | 13 ++++++++++++- 1 file changed, 12 insertions(+), 1 deletion(-) diff --git a/samples/cachingfs/caching_fs.go b/samples/cachingfs/caching_fs.go index b34a26f..ebc7b7c 100644 --- a/samples/cachingfs/caching_fs.go +++ b/samples/cachingfs/caching_fs.go @@ -126,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 @@ -248,7 +251,10 @@ func (fs *cachingFS) SetMtime(mtime time.Time) { // LOCKS_EXCLUDED(fs.mu) func (fs *cachingFS) SetKeepCache(keep bool) { - // TODO + fs.mu.Lock() + defer fs.mu.Unlock() + + fs.keepPageCache = keep } //////////////////////////////////////////////////////////////////////// @@ -350,6 +356,11 @@ 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 } From a88ad8dbf2058d3f9d2c24b325e82d9973eb859a Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Wed, 29 Jul 2015 05:53:46 +0000 Subject: [PATCH 10/11] Added support for KeepPageCache. --- conversions.go | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/conversions.go b/conversions.go index 6ba2f18..3c30d71 100644 --- a/conversions.go +++ b/conversions.go @@ -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 From eacbdb8d5168acad928b117890b309b04bd433b3 Mon Sep 17 00:00:00 2001 From: Aaron Jacobs Date: Wed, 29 Jul 2015 16:24:47 +1000 Subject: [PATCH 11/11] Disabled a test for a feature that doesn't work on OS X. --- fuseops/ops.go | 6 +++++- samples/cachingfs/caching_fs_test.go | 6 ++++++ 2 files changed, 11 insertions(+), 1 deletion(-) diff --git a/fuseops/ops.go b/fuseops/ops.go index 8f67841..f786474 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -456,7 +456,11 @@ type OpenFileOp struct { // a particular inode go through the kernel, set this field to true to // disable this behavior. // - // More discussion: http://goo.gl/cafzWF + // (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 } diff --git a/samples/cachingfs/caching_fs_test.go b/samples/cachingfs/caching_fs_test.go index 5634034..19cdf39 100644 --- a/samples/cachingfs/caching_fs_test.go +++ b/samples/cachingfs/caching_fs_test.go @@ -616,6 +616,12 @@ func (t *PageCacheTest) SingleFileHandle_KeepCache() { 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)