Disabled entry caching on OS X by default, with an opt-in to re-enable it.

This fixes cachingfs tests on OS X, where the entry_valid field returned to
fuse is ignored and entries are cached potentially forever.

More info:
    https://github.com/jacobsa/fuse/issues/1
    https://github.com/osxfuse/osxfuse/issues/199
geesefs-0-30-9
Aaron Jacobs 2015-03-17 16:31:11 +11:00
commit 92569b4f77
3 changed files with 71 additions and 31 deletions

View File

@ -406,6 +406,9 @@ type ChildInodeEntry struct {
// lookup request.
//
// Leave at the zero value to disable caching.
//
// Beware: this value is ignored on OS X, where entry caching is disabled by
// default. See notes on MountConfig.EnableVnodeCaching for more.
EntryExpiration time.Time
}

View File

@ -16,6 +16,7 @@ package fuse
import (
"errors"
"runtime"
"github.com/jacobsa/bazilfuse"
"golang.org/x/net/context"
@ -117,14 +118,30 @@ func (mfs *MountedFileSystem) mountAndServe(
// Optional configuration accepted by Mount.
type MountConfig struct {
// OS X only.
//
// Normally on OS X we mount with the novncache option
// (cf. http://goo.gl/1pTjuk), which disables entry caching in the kernel.
// This is because osxfuse does not support the entry expiration values we
// return to it (cf. http://goo.gl/8yR0Ie) and it is probably better to fail
// to cache than to cache for too long, since the latter is more likely to
// hide consistency bugs that are difficult to detect and diagnose.
//
// This field disables the use of novncache, restoring entry caching. Beware:
// the value of ChildInodeEntry.EntryExpiration is ignored by the kernel, and
// entries will be cached for an arbitrarily long time.
EnableVnodeCaching bool
}
// Convert to mount options to be passed to package bazilfuse.
func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) {
opts = []bazilfuse.MountOption{
// Enable permissions checking in the kernel. See the comments on
// InodeAttributes.Mode.
bazilfuse.SetOption("default_permissions", ""),
// Enable permissions checking in the kernel. See the comments on
// InodeAttributes.Mode.
opts = append(opts, bazilfuse.SetOption("default_permissions", ""))
// OS X only: set novncache when appropriate.
if runtime.GOOS == "darwin" && !c.EnableVnodeCaching {
opts = append(opts, bazilfuse.SetOption("novncache", ""))
}
return

View File

@ -19,6 +19,7 @@ import (
"log"
"os"
"path"
"runtime"
"strings"
"syscall"
"testing"
@ -27,6 +28,7 @@ import (
"github.com/jacobsa/fuse"
"github.com/jacobsa/fuse/samples/cachingfs"
"github.com/jacobsa/gcsfuse/timeutil"
. "github.com/jacobsa/oglematchers"
. "github.com/jacobsa/ogletest"
"golang.org/x/net/context"
)
@ -48,7 +50,8 @@ var _ TearDownInterface = &cachingFSTest{}
func (t *cachingFSTest) setUp(
lookupEntryTimeout time.Duration,
getattrTimeout time.Duration) {
getattrTimeout time.Duration,
config *fuse.MountConfig) {
var err error
// Set up a temporary directory for mounting.
@ -60,7 +63,7 @@ func (t *cachingFSTest) setUp(
AssertEq(nil, err)
// Mount it.
t.mfs, err = fuse.Mount(t.dir, t.fs, &fuse.MountConfig{})
t.mfs, err = fuse.Mount(t.dir, t.fs, config)
AssertEq(nil, err)
err = t.mfs.WaitForReady(context.Background())
@ -168,7 +171,7 @@ func (t *BasicsTest) SetUp(ti *TestInfo) {
getattrTimeout = 0
)
t.cachingFSTest.setUp(lookupEntryTimeout, getattrTimeout)
t.cachingFSTest.setUp(lookupEntryTimeout, getattrTimeout, &fuse.MountConfig{})
}
func (t *BasicsTest) StatNonexistent() {
@ -241,7 +244,7 @@ func (t *NoCachingTest) SetUp(ti *TestInfo) {
getattrTimeout = 0
)
t.cachingFSTest.setUp(lookupEntryTimeout, getattrTimeout)
t.cachingFSTest.setUp(lookupEntryTimeout, getattrTimeout, &fuse.MountConfig{})
}
func (t *NoCachingTest) StatStat() {
@ -318,7 +321,11 @@ func init() { RegisterTestSuite(&EntryCachingTest{}) }
func (t *EntryCachingTest) SetUp(ti *TestInfo) {
t.lookupEntryTimeout = 250 * time.Millisecond
t.cachingFSTest.setUp(t.lookupEntryTimeout, 0)
config := &fuse.MountConfig{
EnableVnodeCaching: true,
}
t.cachingFSTest.setUp(t.lookupEntryTimeout, 0, config)
}
func (t *EntryCachingTest) StatStat() {
@ -348,12 +355,17 @@ func (t *EntryCachingTest) StatRenumberStat() {
// But after waiting for the entry cache to expire, we should see the new
// IDs.
time.Sleep(2 * t.lookupEntryTimeout)
fooAfter, dirAfter, barAfter = t.statAll()
//
// Note that the cache is not guaranteed to expire on darwin. See notes on
// fuse.MountConfig.EnableVnodeCaching.
if runtime.GOOS != "darwin" {
time.Sleep(2 * t.lookupEntryTimeout)
fooAfter, dirAfter, barAfter = t.statAll()
ExpectEq(t.fs.FooID(), getInodeID(fooAfter))
ExpectEq(t.fs.DirID(), getInodeID(dirAfter))
ExpectEq(t.fs.BarID(), getInodeID(barAfter))
ExpectEq(t.fs.FooID(), getInodeID(fooAfter))
ExpectEq(t.fs.DirID(), getInodeID(dirAfter))
ExpectEq(t.fs.BarID(), getInodeID(barAfter))
}
}
func (t *EntryCachingTest) StatMtimeStat() {
@ -390,16 +402,21 @@ func (t *EntryCachingTest) StatRenumberMtimeStat() {
// After waiting for the entry cache to expire, we should see fresh
// everything.
time.Sleep(2 * t.lookupEntryTimeout)
fooAfter, dirAfter, barAfter = t.statAll()
//
// Note that the cache is not guaranteed to expire on darwin. See notes on
// fuse.MountConfig.EnableVnodeCaching.
if runtime.GOOS != "darwin" {
time.Sleep(2 * t.lookupEntryTimeout)
fooAfter, dirAfter, barAfter = t.statAll()
ExpectEq(t.fs.FooID(), getInodeID(fooAfter))
ExpectEq(t.fs.DirID(), getInodeID(dirAfter))
ExpectEq(t.fs.BarID(), getInodeID(barAfter))
ExpectEq(t.fs.FooID(), getInodeID(fooAfter))
ExpectEq(t.fs.DirID(), getInodeID(dirAfter))
ExpectEq(t.fs.BarID(), getInodeID(barAfter))
ExpectThat(fooAfter.ModTime(), timeutil.TimeEq(newMtime))
ExpectThat(dirAfter.ModTime(), timeutil.TimeEq(newMtime))
ExpectThat(barAfter.ModTime(), timeutil.TimeEq(newMtime))
ExpectThat(fooAfter.ModTime(), timeutil.TimeEq(newMtime))
ExpectThat(dirAfter.ModTime(), timeutil.TimeEq(newMtime))
ExpectThat(barAfter.ModTime(), timeutil.TimeEq(newMtime))
}
}
////////////////////////////////////////////////////////////////////////
@ -417,7 +434,7 @@ func init() { RegisterTestSuite(&AttributeCachingTest{}) }
func (t *AttributeCachingTest) SetUp(ti *TestInfo) {
t.getattrTimeout = 250 * time.Millisecond
t.cachingFSTest.setUp(0, t.getattrTimeout)
t.cachingFSTest.setUp(0, t.getattrTimeout, &fuse.MountConfig{})
}
func (t *AttributeCachingTest) StatStat() {
@ -446,18 +463,21 @@ func (t *AttributeCachingTest) StatRenumberStat() {
ExpectEq(t.fs.BarID(), getInodeID(barAfter))
}
func (t *AttributeCachingTest) StatMtimeStat() {
func (t *AttributeCachingTest) StatMtimeStat_ViaPath() {
newMtime := t.initialMtime.Add(time.Second)
t.statAll()
t.fs.SetMtime(newMtime)
fooAfter, dirAfter, barAfter := t.statAll()
// We should see the new attributes, since the entry had to be looked up
// again and the new attributes were returned with the entry.
ExpectThat(fooAfter.ModTime(), timeutil.TimeEq(newMtime))
ExpectThat(dirAfter.ModTime(), timeutil.TimeEq(newMtime))
ExpectThat(barAfter.ModTime(), timeutil.TimeEq(newMtime))
// Since we don't have entry caching enabled, the call above had to look up
// the entry again. With the lookup we returned new attributes, so it's
// possible that the mtime will be fresh. On Linux it appears to be, and on
// OS X it appears to not be.
m := AnyOf(timeutil.TimeEq(newMtime), timeutil.TimeEq(t.initialMtime))
ExpectThat(fooAfter.ModTime(), m)
ExpectThat(dirAfter.ModTime(), m)
ExpectThat(barAfter.ModTime(), m)
}
func (t *AttributeCachingTest) StatMtimeStat_ViaFileDescriptor() {
@ -490,7 +510,7 @@ func (t *AttributeCachingTest) StatMtimeStat_ViaFileDescriptor() {
ExpectThat(barAfter.ModTime(), timeutil.TimeEq(newMtime))
}
func (t *AttributeCachingTest) StatRenumberMtimeStat() {
func (t *AttributeCachingTest) StatRenumberMtimeStat_ViaPath() {
newMtime := t.initialMtime.Add(time.Second)
t.statAll()
@ -500,7 +520,7 @@ func (t *AttributeCachingTest) StatRenumberMtimeStat() {
// We should see new everything, because this is the first time the new
// inodes have been encountered. Entries for the old ones should not have
// been cached.
// been cached, because we have entry caching disabled.
ExpectEq(t.fs.FooID(), getInodeID(fooAfter))
ExpectEq(t.fs.DirID(), getInodeID(dirAfter))
ExpectEq(t.fs.BarID(), getInodeID(barAfter))