Skip to content

Commit

Permalink
darwin: use simpleHandleMap that never reuses handle numbers
Browse files Browse the repository at this point in the history
Implement simpleHandleMap that never reuses handle numbers and
uses a simple map to track handle numbers.

This hopefully works around the

	osxfuse: vnode changed generation

errors we are seeing on darwin, while sacrificing
performance.

rfjakob/gocryptfs#213
hanwen#204
macfuse/macfuse#482
  • Loading branch information
rfjakob committed Mar 13, 2018
1 parent da8b3e4 commit d50a16c
Show file tree
Hide file tree
Showing 6 changed files with 135 additions and 16 deletions.
2 changes: 1 addition & 1 deletion fuse/nodefs/fsconnector.go
Original file line number Diff line number Diff line change
Expand Up @@ -55,7 +55,7 @@ func NewFileSystemConnector(root Node, opts *Options) (c *FileSystemConnector) {
if opts == nil {
opts = NewOptions()
}
c.inodeMap = newPortableHandleMap()
c.inodeMap = newHandleMap()
c.rootNode = newInode(true, root)

c.verify()
Expand Down
5 changes: 4 additions & 1 deletion fuse/nodefs/handle.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,9 @@ import (
"sync"
)

// newHandleMap stores the currently active handleMap implementation.
var newHandleMap = newPortableHandleMap

// HandleMap translates objects in Go space to 64-bit handles that can
// be given out to -say- the linux kernel as NodeIds.
//
Expand Down Expand Up @@ -66,7 +69,7 @@ type portableHandleMap struct {
freeIds []uint64
}

func newPortableHandleMap() *portableHandleMap {
func newPortableHandleMap() handleMap {
return &portableHandleMap{
// Avoid handing out ID 0 and 1.
handles: []*handled{nil, nil},
Expand Down
9 changes: 9 additions & 0 deletions fuse/nodefs/handle_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,9 @@
// Copyright 2018 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package nodefs

func init() {
newHandleMap = newSimpleHandleMap
}
97 changes: 97 additions & 0 deletions fuse/nodefs/handle_simple.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,97 @@
// Copyright 2018 the Go-FUSE Authors. All rights reserved.
// Use of this source code is governed by a BSD-style
// license that can be found in the LICENSE file.

package nodefs

import (
"log"
"sync"
)

// simpleHandleMap is a simple, low-performance handle map that never reuses
// handle numbers.
type simpleHandleMap struct {
sync.Mutex
entries map[uint64]*handled
nextHandle uint64
}

func newSimpleHandleMap() handleMap {
return &simpleHandleMap{
entries: make(map[uint64]*handled),
// Avoid handing out ID 0 and 1, like portableHandleMap does.
nextHandle: 2,
}
}

func (s *simpleHandleMap) Register(obj *handled) (handle, generation uint64) {
s.Lock()
defer s.Unlock()
// The object already has a handle
if obj.count != 0 {
if obj.handle == 0 {
log.Panicf("bug: count=%d handle=%d", obj.count, obj.handle)
}
obj.count++
return obj.handle, 0
}
// Create a new handle
obj.count = 1
obj.handle = s.nextHandle
s.entries[s.nextHandle] = obj
s.nextHandle++
return obj.handle, 0
}

// Count returns the number of currently used handles
func (s *simpleHandleMap) Count() int {
s.Lock()
defer s.Unlock()
return len(s.entries)
}

// Handle gets the object's uint64 handle.
func (s *simpleHandleMap) Handle(obj *handled) (handle uint64) {
s.Lock()
defer s.Unlock()
if obj.count == 0 {
return 0
}
return obj.handle
}

// Decode retrieves a stored object from its uint64 handle.
func (s *simpleHandleMap) Decode(handle uint64) *handled {
s.Lock()
defer s.Unlock()
return s.entries[handle]
}

// Forget decrements the reference counter for "handle" by "count" and drops
// the object if the refcount reaches zero.
// Returns a boolean whether the object was dropped and the object itself.
func (s *simpleHandleMap) Forget(handle uint64, count int) (forgotten bool, obj *handled) {
s.Lock()
defer s.Unlock()
obj = s.entries[handle]
obj.count -= count
if obj.count < 0 {
log.Panicf("underflow: handle %d, count %d, obj.count %d", handle, count, obj.count)
}
if obj.count > 0 {
return false, obj
}
// count is zero, drop the reference
delete(s.entries, handle)
obj.handle = 0
return true, obj
}

// Has checks if the uint64 handle is stored.
func (s *simpleHandleMap) Has(handle uint64) bool {
s.Lock()
defer s.Unlock()
_, ok := s.entries[handle]
return ok
}
36 changes: 23 additions & 13 deletions fuse/nodefs/handle_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,26 +5,32 @@
package nodefs

import (
"strings"
"os"
"testing"
)

func markSeen(t *testing.T, substr string) {
if r := recover(); r != nil {
s := r.(string)
if strings.Contains(s, substr) {
t.Log("expected recovery from: ", r)
} else {
panic(s)
}
var skipTestHandleMapGeneration = false

func TestMain(m *testing.M) {
// Test both handleMap implementations
newHandleMap = newPortableHandleMap
r := m.Run()
if r != 0 {
os.Exit(r)
}
newHandleMap = newSimpleHandleMap
skipTestHandleMapGeneration = true
r = m.Run()
if r != 0 {
os.Exit(r)
}
}

func TestHandleMapLookupCount(t *testing.T) {
for _, portable := range []bool{true, false} {
t.Log("portable:", portable)
v := new(handled)
hm := newPortableHandleMap()
hm := newHandleMap()
h1, g1 := hm.Register(v)
h2, g2 := hm.Register(v)

Expand Down Expand Up @@ -68,7 +74,7 @@ func TestHandleMapLookupCount(t *testing.T) {

func TestHandleMapBasic(t *testing.T) {
v := new(handled)
hm := newPortableHandleMap()
hm := newHandleMap()
h, _ := hm.Register(v)
t.Logf("Got handle 0x%x", h)
if !hm.Has(h) {
Expand All @@ -93,7 +99,7 @@ func TestHandleMapBasic(t *testing.T) {
}

func TestHandleMapMultiple(t *testing.T) {
hm := newPortableHandleMap()
hm := newHandleMap()
for i := 0; i < 10; i++ {
v := &handled{}
h, _ := hm.Register(v)
Expand All @@ -107,7 +113,11 @@ func TestHandleMapMultiple(t *testing.T) {
}

func TestHandleMapGeneration(t *testing.T) {
hm := newPortableHandleMap()
if skipTestHandleMapGeneration {
t.Skip("simpleHandleMap never reuses handles")
}

hm := newHandleMap()

h1, g1 := hm.Register(&handled{})

Expand Down
2 changes: 1 addition & 1 deletion fuse/nodefs/inode.go
Original file line number Diff line number Diff line change
Expand Up @@ -220,7 +220,7 @@ func (n *Inode) rmChild(name string) *Inode {
// Can only be called on untouched root inodes.
func (n *Inode) mountFs(opts *Options) {
n.mountPoint = &fileSystemMount{
openFiles: newPortableHandleMap(),
openFiles: newHandleMap(),
mountInode: n,
options: opts,
}
Expand Down

0 comments on commit d50a16c

Please sign in to comment.