Skip to content

Commit 7070f5a

Browse files
authored
Fix file handle leak in read (#139)
* [bug] Close file when reading * Test close-on-read fix
1 parent 96c0603 commit 7070f5a

File tree

2 files changed

+91
-3
lines changed

2 files changed

+91
-3
lines changed

nfs_onread.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -49,6 +49,7 @@ func onRead(ctx context.Context, w *response, userHandle Handler) error {
4949
}
5050
return &NFSStatusError{NFSStatusAccess, err}
5151
}
52+
defer fh.Close()
5253

5354
resp := nfsReadResponse{}
5455

nfs_test.go

Lines changed: 90 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,11 +3,15 @@ package nfs_test
33
import (
44
"bytes"
55
"fmt"
6+
"math/rand"
67
"net"
8+
"os"
79
"reflect"
810
"sort"
11+
"sync"
912
"testing"
1013

14+
"github.com/go-git/go-billy/v5"
1115
nfs "github.com/willscott/go-nfs"
1216
"github.com/willscott/go-nfs/helpers"
1317
"github.com/willscott/go-nfs/helpers/memfs"
@@ -18,6 +22,75 @@ import (
1822
"github.com/willscott/go-nfs-client/nfs/xdr"
1923
)
2024

25+
type OpenArgs struct {
26+
File string
27+
Flag int
28+
Perm os.FileMode
29+
}
30+
31+
func (o *OpenArgs) String() string {
32+
return fmt.Sprintf("\"%s\"; %05xd %s", o.File, o.Flag, o.Perm)
33+
}
34+
35+
// NewTrackingFS wraps fs to detect file handle leaks.
36+
func NewTrackingFS(fs billy.Filesystem) *trackingFS {
37+
return &trackingFS{Filesystem: fs, open: make(map[int64]OpenArgs)}
38+
}
39+
40+
// trackingFS wraps a Filesystem to detect file handle leaks.
41+
type trackingFS struct {
42+
billy.Filesystem
43+
mu sync.Mutex
44+
open map[int64]OpenArgs
45+
}
46+
47+
func (t *trackingFS) ListOpened() []OpenArgs {
48+
t.mu.Lock()
49+
defer t.mu.Unlock()
50+
ret := make([]OpenArgs, 0, len(t.open))
51+
for _, o := range t.open {
52+
ret = append(ret, o)
53+
}
54+
return ret
55+
}
56+
57+
func (t *trackingFS) Create(filename string) (billy.File, error) {
58+
return t.OpenFile(filename, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666)
59+
}
60+
61+
func (t *trackingFS) Open(filename string) (billy.File, error) {
62+
return t.OpenFile(filename, os.O_RDONLY, 0)
63+
}
64+
65+
func (t *trackingFS) OpenFile(filename string, flag int, perm os.FileMode) (billy.File, error) {
66+
open, err := t.Filesystem.OpenFile(filename, flag, perm)
67+
if err != nil {
68+
return nil, err
69+
}
70+
t.mu.Lock()
71+
defer t.mu.Unlock()
72+
id := rand.Int63()
73+
t.open[id] = OpenArgs{filename, flag, perm}
74+
closer := func() {
75+
delete(t.open, id)
76+
}
77+
open = &trackingFile{
78+
File: open,
79+
onClose: closer,
80+
}
81+
return open, err
82+
}
83+
84+
type trackingFile struct {
85+
billy.File
86+
onClose func()
87+
}
88+
89+
func (f *trackingFile) Close() error {
90+
f.onClose()
91+
return f.File.Close()
92+
}
93+
2194
func TestNFS(t *testing.T) {
2295
if testing.Verbose() {
2396
util.DefaultLogger.SetDebug(true)
@@ -29,9 +102,17 @@ func TestNFS(t *testing.T) {
29102
t.Fatal(err)
30103
}
31104

32-
mem := memfs.New()
105+
mem := NewTrackingFS(memfs.New())
106+
107+
defer func() {
108+
if opened := mem.ListOpened(); len(opened) > 0 {
109+
t.Errorf("Unclosed files: %v", opened)
110+
}
111+
}()
112+
33113
// File needs to exist in the root for memfs to acknowledge the root exists.
34-
_, _ = mem.Create("/test")
114+
r, _ := mem.Create("/test")
115+
r.Close()
35116

36117
handler := helpers.NewNullAuthHandler(mem)
37118
cacheHelper := helpers.NewCachingHandler(handler, 1024)
@@ -78,12 +159,18 @@ func TestNFS(t *testing.T) {
78159
if err != nil {
79160
t.Fatal(err)
80161
}
162+
defer f.Close()
81163
b := []byte("hello world")
82164
_, err = f.Write(b)
83165
if err != nil {
84166
t.Fatal(err)
85167
}
86-
mf, _ := mem.Open("/helloworld.txt")
168+
169+
mf, err := target.Open("/helloworld.txt")
170+
if err != nil {
171+
t.Fatal(err)
172+
}
173+
defer mf.Close()
87174
buf := make([]byte, len(b))
88175
if _, err = mf.Read(buf[:]); err != nil {
89176
t.Fatal(err)

0 commit comments

Comments
 (0)