diff --git a/fuseops/ops.go b/fuseops/ops.go index f8ff386..cd21c0e 100644 --- a/fuseops/ops.go +++ b/fuseops/ops.go @@ -237,7 +237,7 @@ func (o *SetInodeAttributesOp) toBazilfuseResponse() (bfResp interface{}) { // revalidating. // // In contrast to all other inodes, RootInodeID begins with an implicit -// reference count of one, without a corresponding op to increase it. (There +// lookup count of one, without a corresponding op to increase it. (There // could be no such op, because the root cannot be referred to by name.) Code // walk: // @@ -248,10 +248,10 @@ func (o *SetInodeAttributesOp) toBazilfuseResponse() (bfResp interface{}) { // // * (http://goo.gl/vPD9Oh) fuse_iget increments nlookup. // -// File systems should not make assumptions about whether they will or will not -// receive a ForgetInodeOp for the root inode. Experimentally, OS X seems to -// never send one, while Linux appears to send one only sometimes. (Cf. -// http://goo.gl/EUbxEg, fuse-devel thread "Root inode lookup count"). +// File systems should tolerate but not rely on receiving forget ops for +// remaining inodes when the file system unmounts, including the root inode. +// Rather they should take fuse.Connection.ReadOp returning io.EOF as +// implicitly decrementing all lookup counts to zero. type ForgetInodeOp struct { commonOp diff --git a/fuseutil/file_system.go b/fuseutil/file_system.go index b8d894e..18510aa 100644 --- a/fuseutil/file_system.go +++ b/fuseutil/file_system.go @@ -60,6 +60,11 @@ type FileSystem interface { FlushFile(*fuseops.FlushFileOp) error ReleaseFileHandle(*fuseops.ReleaseFileHandleOp) error ReadSymlink(*fuseops.ReadSymlinkOp) error + + // Regard all inodes (including the root inode) as having their lookup counts + // decremented to zero, and clean up any resources associated with the file + // system. No further calls to the file system will be made. + Destroy() } // Create a fuse.Server that handles ops by calling the associated FileSystem @@ -85,7 +90,12 @@ type fileSystemServer struct { } func (s *fileSystemServer) ServeOps(c *fuse.Connection) { - defer s.opsInFlight.Wait() + // When we are done, we clean up by waiting for all in-flight ops then + // destroying the file system. + defer func() { + s.opsInFlight.Wait() + s.fs.Destroy() + }() for { op, err := c.ReadOp() diff --git a/fuseutil/not_implemented_file_system.go b/fuseutil/not_implemented_file_system.go index e4abb5f..9d12e21 100644 --- a/fuseutil/not_implemented_file_system.go +++ b/fuseutil/not_implemented_file_system.go @@ -147,3 +147,6 @@ func (fs *NotImplementedFileSystem) ReadSymlink( err = fuse.ENOSYS return } + +func (fs *NotImplementedFileSystem) Destroy() { +} diff --git a/mounted_file_system.go b/mounted_file_system.go index 4055a3d..9335cd9 100644 --- a/mounted_file_system.go +++ b/mounted_file_system.go @@ -27,7 +27,8 @@ import ( // A type that knows how to serve ops read from a connection. type Server interface { // Read and serve ops from the supplied connection until EOF. Do not return - // until all operations have been responded to. + // until all operations have been responded to. Must not be called more than + // once. ServeOps(*Connection) } diff --git a/samples/forgetfs/forget_fs.go b/samples/forgetfs/forget_fs.go index 997ff48..52ead10 100644 --- a/samples/forgetfs/forget_fs.go +++ b/samples/forgetfs/forget_fs.go @@ -17,7 +17,6 @@ package forgetfs import ( "fmt" "os" - "runtime" "github.com/jacobsa/fuse" "github.com/jacobsa/fuse/fuseops" @@ -67,6 +66,13 @@ func NewFileSystem() (fs *ForgetFS) { // The root inode starts with a lookup count of one. impl.inodes[cannedID_Root].IncrementLookupCount() + // The canned inodes are supposed to be stable from the user's point of view, + // so we should allow them to be looked up at any point even if the kernel + // has balanced its lookups with its forgets. Ensure that they never go to + // zero until the file system is destroyed. + impl.inodes[cannedID_Foo].IncrementLookupCount() + impl.inodes[cannedID_Bar].IncrementLookupCount() + // Set up the mutex. impl.mu = syncutil.NewInvariantMutex(impl.checkInvariants) @@ -165,6 +171,11 @@ func (in *inode) DecrementLookupCount(n uint64) { in.lookupCount -= n } +// Decrement the lookup count to zero. +func (in *inode) Destroy() { + in.DecrementLookupCount(in.lookupCount) +} + //////////////////////////////////////////////////////////////////////// // Helpers //////////////////////////////////////////////////////////////////////// @@ -184,30 +195,7 @@ func (fs *fsImpl) Check() { fs.mu.Lock() defer fs.mu.Unlock() - // On Linux we often don't receive forget ops, and never receive destroy ops - // (cf. http://goo.gl/EUbxEg, fuse-devel thread "Root inode lookup count"). - // So there's not really much we can check here. - // - // TODO(jacobsa): Figure out why we don't receive destroy. If we can reliably - // receive it, we can treat it as "forget all". - if runtime.GOOS == "linux" { - return - } - for k, v := range fs.inodes { - // Special case: we don't require the root inode to have reached zero. - // OS X doesn't seem to send forgets for the root, and Linux only does - // sometimes. But we want to make sure it's not greater than one, which - // would be weird. - if k == fuseops.RootInodeID { - if v.lookupCount > 1 { - panic(fmt.Sprintf("Root has lookup count %v", v.lookupCount)) - } - - continue - } - - // Check other inodes. if v.lookupCount != 0 { panic(fmt.Sprintf("Inode %v has lookup count %v", k, v.lookupCount)) } @@ -383,3 +371,9 @@ func (fs *fsImpl) OpenDir( return } + +func (fs *fsImpl) Destroy() { + for _, in := range fs.inodes { + in.Destroy() + } +}