Skip to content

Commit a009503

Browse files
committed
darwin: use simpleHandleMap that never reuses handle numbers
Implement simpleHandleMap that never reuses handle numbers and uses a simple map to track handle numbers. This seems to get rid of the osxfuse: vnode changed generation errors we are seeing on darwin, while sacrificing some performance. I have tested simpleHandleMap on darwin and linux, seems to work fine. rfjakob/gocryptfs#213 hanwen#204 macfuse/macfuse#482
1 parent da8b3e4 commit a009503

File tree

6 files changed

+141
-16
lines changed

6 files changed

+141
-16
lines changed

fuse/nodefs/fsconnector.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -55,7 +55,7 @@ func NewFileSystemConnector(root Node, opts *Options) (c *FileSystemConnector) {
5555
if opts == nil {
5656
opts = NewOptions()
5757
}
58-
c.inodeMap = newPortableHandleMap()
58+
c.inodeMap = newHandleMap()
5959
c.rootNode = newInode(true, root)
6060

6161
c.verify()

fuse/nodefs/handle.go

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,9 @@ import (
99
"sync"
1010
)
1111

12+
// newHandleMap stores the currently active handleMap implementation.
13+
var newHandleMap = newPortableHandleMap
14+
1215
// HandleMap translates objects in Go space to 64-bit handles that can
1316
// be given out to -say- the linux kernel as NodeIds.
1417
//
@@ -66,7 +69,7 @@ type portableHandleMap struct {
6669
freeIds []uint64
6770
}
6871

69-
func newPortableHandleMap() *portableHandleMap {
72+
func newPortableHandleMap() handleMap {
7073
return &portableHandleMap{
7174
// Avoid handing out ID 0 and 1.
7275
handles: []*handled{nil, nil},

fuse/nodefs/handle_darwin.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
// Copyright 2018 the Go-FUSE Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package nodefs
6+
7+
func init() {
8+
// darwin / osxfuse has problems with handle reuse, we get
9+
// the kernel message
10+
// osxfuse: vnode changed generation
11+
// and errors returned to the user. simpleHandleMap never reuses handles
12+
// which seems to keep osxfuse happy.
13+
// https://github.com/hanwen/go-fuse/issues/204
14+
newHandleMap = newSimpleHandleMap
15+
}

fuse/nodefs/handle_simple.go

Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
// Copyright 2018 the Go-FUSE Authors. All rights reserved.
2+
// Use of this source code is governed by a BSD-style
3+
// license that can be found in the LICENSE file.
4+
5+
package nodefs
6+
7+
import (
8+
"log"
9+
"sync"
10+
)
11+
12+
// simpleHandleMap is a simple, low-performance handle map that never reuses
13+
// handle numbers.
14+
type simpleHandleMap struct {
15+
sync.Mutex
16+
entries map[uint64]*handled
17+
nextHandle uint64
18+
}
19+
20+
func newSimpleHandleMap() handleMap {
21+
return &simpleHandleMap{
22+
entries: make(map[uint64]*handled),
23+
// Avoid handing out ID 0 and 1, like portableHandleMap does.
24+
nextHandle: 2,
25+
}
26+
}
27+
28+
func (s *simpleHandleMap) Register(obj *handled) (handle, generation uint64) {
29+
s.Lock()
30+
defer s.Unlock()
31+
// The object already has a handle
32+
if obj.count != 0 {
33+
if obj.handle == 0 {
34+
log.Panicf("bug: count=%d handle=%d", obj.count, obj.handle)
35+
}
36+
obj.count++
37+
return obj.handle, 0
38+
}
39+
// Create a new handle
40+
obj.count = 1
41+
obj.handle = s.nextHandle
42+
s.entries[s.nextHandle] = obj
43+
s.nextHandle++
44+
return obj.handle, 0
45+
}
46+
47+
// Count returns the number of currently used handles
48+
func (s *simpleHandleMap) Count() int {
49+
s.Lock()
50+
defer s.Unlock()
51+
return len(s.entries)
52+
}
53+
54+
// Handle gets the object's uint64 handle.
55+
func (s *simpleHandleMap) Handle(obj *handled) (handle uint64) {
56+
s.Lock()
57+
defer s.Unlock()
58+
if obj.count == 0 {
59+
return 0
60+
}
61+
return obj.handle
62+
}
63+
64+
// Decode retrieves a stored object from its uint64 handle.
65+
func (s *simpleHandleMap) Decode(handle uint64) *handled {
66+
s.Lock()
67+
defer s.Unlock()
68+
return s.entries[handle]
69+
}
70+
71+
// Forget decrements the reference counter for "handle" by "count" and drops
72+
// the object if the refcount reaches zero.
73+
// Returns a boolean whether the object was dropped and the object itself.
74+
func (s *simpleHandleMap) Forget(handle uint64, count int) (forgotten bool, obj *handled) {
75+
s.Lock()
76+
defer s.Unlock()
77+
obj = s.entries[handle]
78+
obj.count -= count
79+
if obj.count < 0 {
80+
log.Panicf("underflow: handle %d, count %d, obj.count %d", handle, count, obj.count)
81+
}
82+
if obj.count > 0 {
83+
return false, obj
84+
}
85+
// count is zero, drop the reference
86+
delete(s.entries, handle)
87+
obj.handle = 0
88+
return true, obj
89+
}
90+
91+
// Has checks if the uint64 handle is stored.
92+
func (s *simpleHandleMap) Has(handle uint64) bool {
93+
s.Lock()
94+
defer s.Unlock()
95+
_, ok := s.entries[handle]
96+
return ok
97+
}

fuse/nodefs/handle_test.go

Lines changed: 23 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -5,26 +5,32 @@
55
package nodefs
66

77
import (
8-
"strings"
8+
"os"
99
"testing"
1010
)
1111

12-
func markSeen(t *testing.T, substr string) {
13-
if r := recover(); r != nil {
14-
s := r.(string)
15-
if strings.Contains(s, substr) {
16-
t.Log("expected recovery from: ", r)
17-
} else {
18-
panic(s)
19-
}
12+
var skipTestHandleMapGeneration = false
13+
14+
func TestMain(m *testing.M) {
15+
// Test both handleMap implementations
16+
newHandleMap = newPortableHandleMap
17+
r := m.Run()
18+
if r != 0 {
19+
os.Exit(r)
20+
}
21+
newHandleMap = newSimpleHandleMap
22+
skipTestHandleMapGeneration = true
23+
r = m.Run()
24+
if r != 0 {
25+
os.Exit(r)
2026
}
2127
}
2228

2329
func TestHandleMapLookupCount(t *testing.T) {
2430
for _, portable := range []bool{true, false} {
2531
t.Log("portable:", portable)
2632
v := new(handled)
27-
hm := newPortableHandleMap()
33+
hm := newHandleMap()
2834
h1, g1 := hm.Register(v)
2935
h2, g2 := hm.Register(v)
3036

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

6975
func TestHandleMapBasic(t *testing.T) {
7076
v := new(handled)
71-
hm := newPortableHandleMap()
77+
hm := newHandleMap()
7278
h, _ := hm.Register(v)
7379
t.Logf("Got handle 0x%x", h)
7480
if !hm.Has(h) {
@@ -93,7 +99,7 @@ func TestHandleMapBasic(t *testing.T) {
9399
}
94100

95101
func TestHandleMapMultiple(t *testing.T) {
96-
hm := newPortableHandleMap()
102+
hm := newHandleMap()
97103
for i := 0; i < 10; i++ {
98104
v := &handled{}
99105
h, _ := hm.Register(v)
@@ -107,7 +113,11 @@ func TestHandleMapMultiple(t *testing.T) {
107113
}
108114

109115
func TestHandleMapGeneration(t *testing.T) {
110-
hm := newPortableHandleMap()
116+
if skipTestHandleMapGeneration {
117+
t.Skip("simpleHandleMap never reuses handles")
118+
}
119+
120+
hm := newHandleMap()
111121

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

fuse/nodefs/inode.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -220,7 +220,7 @@ func (n *Inode) rmChild(name string) *Inode {
220220
// Can only be called on untouched root inodes.
221221
func (n *Inode) mountFs(opts *Options) {
222222
n.mountPoint = &fileSystemMount{
223-
openFiles: newPortableHandleMap(),
223+
openFiles: newHandleMap(),
224224
mountInode: n,
225225
options: opts,
226226
}

0 commit comments

Comments
 (0)