fs: Add new cache policy "remote_revalidate".

This CL adds a new cache-policy for gofer filesystems that uses the host page
cache, but causes dirents to be reloaded on each Walk, and does not cache
readdir results.

This policy is useful when the remote filesystem may change out from underneath
us, as any remote changes will be reflected on the next Walk.

Importantly, this cache policy is only consistent if we do not use gVisor's
internal page cache, since that page cache is tied to the Inode and may be
thrown away upon Revalidation.

This cache policy should only be used when the gofer supports donating host
FDs, since then gVisor will make use of the host kernel page cache, which will
be consistent for all open files in the gofer. In fact, a panic will be raised
if a file is opened without a donated FD.

PiperOrigin-RevId: 207752937
Change-Id: I233cb78b4695bbe00a4605ae64080a47629329b8
This commit is contained in:
Nicolas Lacasse
2018-08-07 11:43:41 -07:00
committed by Shentubot
parent c348d07863
commit a38f41b464
3 changed files with 392 additions and 232 deletions
+41 -5
View File
@@ -34,8 +34,34 @@ const (
// Use virtual file system cache for everything, but send writes to the
// fs agent immediately.
cacheAllWritethrough
// Use virtual file system cache for everything, but reload dirents
// from the remote filesystem on each lookup. Thus, if the remote
// filesystem has changed, the returned dirent will have the updated
// state.
//
// This policy should *only* be used with remote filesystems that
// donate their host FDs to the sandbox and thus use the host page
// cache, otherwise the dirent state will be inconsistent.
cacheRemoteRevalidating
)
// String returns the string name of the cache policy.
func (cp cachePolicy) String() string {
switch cp {
case cacheNone:
return "cacheNone"
case cacheAll:
return "cacheAll"
case cacheAllWritethrough:
return "cacheAllWritethrough"
case cacheRemoteRevalidating:
return "cacheRemoteRevalidating"
default:
return "unknown"
}
}
func parseCachePolicy(policy string) (cachePolicy, error) {
switch policy {
case "fscache":
@@ -44,6 +70,8 @@ func parseCachePolicy(policy string) (cachePolicy, error) {
return cacheNone, nil
case "fscache_writethrough":
return cacheAllWritethrough, nil
case "remote_revalidating":
return cacheRemoteRevalidating, nil
}
return cacheNone, fmt.Errorf("unsupported cache mode: %s", policy)
}
@@ -63,14 +91,16 @@ func (cp cachePolicy) cacheReaddir() bool {
}
// usePageCache determines whether the page cache should be used for the given
// inode.
// inode. If the remote filesystem donates host FDs to the sentry, then the
// host kernel's page cache will be used, otherwise we will use a
// sentry-internal page cache.
func (cp cachePolicy) usePageCache(inode *fs.Inode) bool {
// Do cached IO for regular files only. Some "character devices" expect
// no caching.
if !fs.IsFile(inode.StableAttr) {
return false
}
return cp == cacheAll || cp == cacheAllWritethrough
return cp == cacheAll || cp == cacheAllWritethrough || cp == cacheRemoteRevalidating
}
// writeThough indicates whether writes to the file should be synced to the
@@ -79,10 +109,16 @@ func (cp cachePolicy) writeThrough(inode *fs.Inode) bool {
return cp == cacheNone || cp == cacheAllWritethrough
}
// revalidateDirent indicates that dirents should be revalidated after they are
// looked up.
// revalidateDirent indicates that a dirent should be revalidated after a
// lookup, because the looked up version may be stale.
func (cp cachePolicy) revalidateDirent() bool {
return cp == cacheNone
if cp == cacheAll || cp == cacheAllWritethrough {
return false
}
// TODO: The cacheRemoteRevalidating policy should only
// return true if the remote file's attributes have changed.
return true
}
// keepDirent indicates that dirents should be kept pinned in the dirent tree
+12 -1
View File
@@ -15,6 +15,7 @@
package gofer
import (
"fmt"
"syscall"
"gvisor.googlesource.com/gvisor/pkg/log"
@@ -72,6 +73,17 @@ func NewFile(ctx context.Context, dirent *fs.Dirent, name string, flags fs.FileF
flags.Pread = true
flags.Pwrite = true
if fs.IsFile(dirent.Inode.StableAttr) {
// If cache policy is "remote revalidating", then we must
// ensure that we have a host FD. Otherwise, the
// sentry-internal page cache will be used, and we can end up
// in an inconsistent state if the remote file changes.
cp := dirent.Inode.InodeOperations.(*inodeOperations).session().cachePolicy
if cp == cacheRemoteRevalidating && handles.Host == nil {
panic(fmt.Sprintf("remote-revalidating cache policy requires gofer to donate host FD, but file %q did not have host FD", name))
}
}
f := &fileOperations{
inodeOperations: i,
handles: handles,
@@ -202,7 +214,6 @@ func (f *fileOperations) Write(ctx context.Context, file *fs.File, src usermem.I
err = f.inodeOperations.cachingInodeOps.WriteOut(ctx, file.Dirent.Inode)
}
return n, err
}
return src.CopyInTo(ctx, f.handles.readWriterAt(ctx, offset))
}
File diff suppressed because it is too large Load Diff