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/199geesefs-0-30-9
commit
92569b4f77
|
@ -406,6 +406,9 @@ type ChildInodeEntry struct {
|
||||||
// lookup request.
|
// lookup request.
|
||||||
//
|
//
|
||||||
// Leave at the zero value to disable caching.
|
// 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
|
EntryExpiration time.Time
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -16,6 +16,7 @@ package fuse
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
|
"runtime"
|
||||||
|
|
||||||
"github.com/jacobsa/bazilfuse"
|
"github.com/jacobsa/bazilfuse"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
|
@ -117,14 +118,30 @@ func (mfs *MountedFileSystem) mountAndServe(
|
||||||
|
|
||||||
// Optional configuration accepted by Mount.
|
// Optional configuration accepted by Mount.
|
||||||
type MountConfig struct {
|
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.
|
// Convert to mount options to be passed to package bazilfuse.
|
||||||
func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) {
|
func (c *MountConfig) bazilfuseOptions() (opts []bazilfuse.MountOption) {
|
||||||
opts = []bazilfuse.MountOption{
|
// Enable permissions checking in the kernel. See the comments on
|
||||||
// Enable permissions checking in the kernel. See the comments on
|
// InodeAttributes.Mode.
|
||||||
// InodeAttributes.Mode.
|
opts = append(opts, bazilfuse.SetOption("default_permissions", ""))
|
||||||
bazilfuse.SetOption("default_permissions", ""),
|
|
||||||
|
// OS X only: set novncache when appropriate.
|
||||||
|
if runtime.GOOS == "darwin" && !c.EnableVnodeCaching {
|
||||||
|
opts = append(opts, bazilfuse.SetOption("novncache", ""))
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|
|
@ -19,6 +19,7 @@ import (
|
||||||
"log"
|
"log"
|
||||||
"os"
|
"os"
|
||||||
"path"
|
"path"
|
||||||
|
"runtime"
|
||||||
"strings"
|
"strings"
|
||||||
"syscall"
|
"syscall"
|
||||||
"testing"
|
"testing"
|
||||||
|
@ -27,6 +28,7 @@ import (
|
||||||
"github.com/jacobsa/fuse"
|
"github.com/jacobsa/fuse"
|
||||||
"github.com/jacobsa/fuse/samples/cachingfs"
|
"github.com/jacobsa/fuse/samples/cachingfs"
|
||||||
"github.com/jacobsa/gcsfuse/timeutil"
|
"github.com/jacobsa/gcsfuse/timeutil"
|
||||||
|
. "github.com/jacobsa/oglematchers"
|
||||||
. "github.com/jacobsa/ogletest"
|
. "github.com/jacobsa/ogletest"
|
||||||
"golang.org/x/net/context"
|
"golang.org/x/net/context"
|
||||||
)
|
)
|
||||||
|
@ -48,7 +50,8 @@ var _ TearDownInterface = &cachingFSTest{}
|
||||||
|
|
||||||
func (t *cachingFSTest) setUp(
|
func (t *cachingFSTest) setUp(
|
||||||
lookupEntryTimeout time.Duration,
|
lookupEntryTimeout time.Duration,
|
||||||
getattrTimeout time.Duration) {
|
getattrTimeout time.Duration,
|
||||||
|
config *fuse.MountConfig) {
|
||||||
var err error
|
var err error
|
||||||
|
|
||||||
// Set up a temporary directory for mounting.
|
// Set up a temporary directory for mounting.
|
||||||
|
@ -60,7 +63,7 @@ func (t *cachingFSTest) setUp(
|
||||||
AssertEq(nil, err)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
// Mount it.
|
// 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)
|
AssertEq(nil, err)
|
||||||
|
|
||||||
err = t.mfs.WaitForReady(context.Background())
|
err = t.mfs.WaitForReady(context.Background())
|
||||||
|
@ -168,7 +171,7 @@ func (t *BasicsTest) SetUp(ti *TestInfo) {
|
||||||
getattrTimeout = 0
|
getattrTimeout = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
t.cachingFSTest.setUp(lookupEntryTimeout, getattrTimeout)
|
t.cachingFSTest.setUp(lookupEntryTimeout, getattrTimeout, &fuse.MountConfig{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *BasicsTest) StatNonexistent() {
|
func (t *BasicsTest) StatNonexistent() {
|
||||||
|
@ -241,7 +244,7 @@ func (t *NoCachingTest) SetUp(ti *TestInfo) {
|
||||||
getattrTimeout = 0
|
getattrTimeout = 0
|
||||||
)
|
)
|
||||||
|
|
||||||
t.cachingFSTest.setUp(lookupEntryTimeout, getattrTimeout)
|
t.cachingFSTest.setUp(lookupEntryTimeout, getattrTimeout, &fuse.MountConfig{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *NoCachingTest) StatStat() {
|
func (t *NoCachingTest) StatStat() {
|
||||||
|
@ -318,7 +321,11 @@ func init() { RegisterTestSuite(&EntryCachingTest{}) }
|
||||||
|
|
||||||
func (t *EntryCachingTest) SetUp(ti *TestInfo) {
|
func (t *EntryCachingTest) SetUp(ti *TestInfo) {
|
||||||
t.lookupEntryTimeout = 250 * time.Millisecond
|
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() {
|
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
|
// But after waiting for the entry cache to expire, we should see the new
|
||||||
// IDs.
|
// 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.FooID(), getInodeID(fooAfter))
|
||||||
ExpectEq(t.fs.DirID(), getInodeID(dirAfter))
|
ExpectEq(t.fs.DirID(), getInodeID(dirAfter))
|
||||||
ExpectEq(t.fs.BarID(), getInodeID(barAfter))
|
ExpectEq(t.fs.BarID(), getInodeID(barAfter))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *EntryCachingTest) StatMtimeStat() {
|
func (t *EntryCachingTest) StatMtimeStat() {
|
||||||
|
@ -390,16 +402,21 @@ func (t *EntryCachingTest) StatRenumberMtimeStat() {
|
||||||
|
|
||||||
// After waiting for the entry cache to expire, we should see fresh
|
// After waiting for the entry cache to expire, we should see fresh
|
||||||
// everything.
|
// 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.FooID(), getInodeID(fooAfter))
|
||||||
ExpectEq(t.fs.DirID(), getInodeID(dirAfter))
|
ExpectEq(t.fs.DirID(), getInodeID(dirAfter))
|
||||||
ExpectEq(t.fs.BarID(), getInodeID(barAfter))
|
ExpectEq(t.fs.BarID(), getInodeID(barAfter))
|
||||||
|
|
||||||
ExpectThat(fooAfter.ModTime(), timeutil.TimeEq(newMtime))
|
ExpectThat(fooAfter.ModTime(), timeutil.TimeEq(newMtime))
|
||||||
ExpectThat(dirAfter.ModTime(), timeutil.TimeEq(newMtime))
|
ExpectThat(dirAfter.ModTime(), timeutil.TimeEq(newMtime))
|
||||||
ExpectThat(barAfter.ModTime(), timeutil.TimeEq(newMtime))
|
ExpectThat(barAfter.ModTime(), timeutil.TimeEq(newMtime))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
////////////////////////////////////////////////////////////////////////
|
////////////////////////////////////////////////////////////////////////
|
||||||
|
@ -417,7 +434,7 @@ func init() { RegisterTestSuite(&AttributeCachingTest{}) }
|
||||||
|
|
||||||
func (t *AttributeCachingTest) SetUp(ti *TestInfo) {
|
func (t *AttributeCachingTest) SetUp(ti *TestInfo) {
|
||||||
t.getattrTimeout = 250 * time.Millisecond
|
t.getattrTimeout = 250 * time.Millisecond
|
||||||
t.cachingFSTest.setUp(0, t.getattrTimeout)
|
t.cachingFSTest.setUp(0, t.getattrTimeout, &fuse.MountConfig{})
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *AttributeCachingTest) StatStat() {
|
func (t *AttributeCachingTest) StatStat() {
|
||||||
|
@ -446,18 +463,21 @@ func (t *AttributeCachingTest) StatRenumberStat() {
|
||||||
ExpectEq(t.fs.BarID(), getInodeID(barAfter))
|
ExpectEq(t.fs.BarID(), getInodeID(barAfter))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *AttributeCachingTest) StatMtimeStat() {
|
func (t *AttributeCachingTest) StatMtimeStat_ViaPath() {
|
||||||
newMtime := t.initialMtime.Add(time.Second)
|
newMtime := t.initialMtime.Add(time.Second)
|
||||||
|
|
||||||
t.statAll()
|
t.statAll()
|
||||||
t.fs.SetMtime(newMtime)
|
t.fs.SetMtime(newMtime)
|
||||||
fooAfter, dirAfter, barAfter := t.statAll()
|
fooAfter, dirAfter, barAfter := t.statAll()
|
||||||
|
|
||||||
// We should see the new attributes, since the entry had to be looked up
|
// Since we don't have entry caching enabled, the call above had to look up
|
||||||
// again and the new attributes were returned with the entry.
|
// the entry again. With the lookup we returned new attributes, so it's
|
||||||
ExpectThat(fooAfter.ModTime(), timeutil.TimeEq(newMtime))
|
// possible that the mtime will be fresh. On Linux it appears to be, and on
|
||||||
ExpectThat(dirAfter.ModTime(), timeutil.TimeEq(newMtime))
|
// OS X it appears to not be.
|
||||||
ExpectThat(barAfter.ModTime(), timeutil.TimeEq(newMtime))
|
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() {
|
func (t *AttributeCachingTest) StatMtimeStat_ViaFileDescriptor() {
|
||||||
|
@ -490,7 +510,7 @@ func (t *AttributeCachingTest) StatMtimeStat_ViaFileDescriptor() {
|
||||||
ExpectThat(barAfter.ModTime(), timeutil.TimeEq(newMtime))
|
ExpectThat(barAfter.ModTime(), timeutil.TimeEq(newMtime))
|
||||||
}
|
}
|
||||||
|
|
||||||
func (t *AttributeCachingTest) StatRenumberMtimeStat() {
|
func (t *AttributeCachingTest) StatRenumberMtimeStat_ViaPath() {
|
||||||
newMtime := t.initialMtime.Add(time.Second)
|
newMtime := t.initialMtime.Add(time.Second)
|
||||||
|
|
||||||
t.statAll()
|
t.statAll()
|
||||||
|
@ -500,7 +520,7 @@ func (t *AttributeCachingTest) StatRenumberMtimeStat() {
|
||||||
|
|
||||||
// We should see new everything, because this is the first time the new
|
// 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
|
// 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.FooID(), getInodeID(fooAfter))
|
||||||
ExpectEq(t.fs.DirID(), getInodeID(dirAfter))
|
ExpectEq(t.fs.DirID(), getInodeID(dirAfter))
|
||||||
ExpectEq(t.fs.BarID(), getInodeID(barAfter))
|
ExpectEq(t.fs.BarID(), getInodeID(barAfter))
|
||||||
|
|
Loading…
Reference in New Issue