Skip to content

Commit fb03afc

Browse files
committed
gitbase: close siva FS after use
When using siva filesystems the FS itself was closed by the garbage collector. This caused to reach opened files limit when the tables did not trigger garbage collection for some time. One of these cases is getting remotes: select * from remotes; Now when opening a siva FS a function is added to the repository to close it. Also added tests to check that the files are indeed closed. repository and go-billy-siva are mocked to keep track of opened files. Signed-off-by: Javi Fontan <[email protected]>
1 parent 59b622a commit fb03afc

23 files changed

+440
-47
lines changed

blobs.go

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -396,6 +396,15 @@ func (i *blobsKeyValueIter) Close() error {
396396
if i.blobs != nil {
397397
i.blobs.Close()
398398
}
399+
400+
if i.idx != nil {
401+
i.idx.Close()
402+
}
403+
404+
if i.repo != nil {
405+
i.repo.Close()
406+
}
407+
399408
return nil
400409
}
401410

blobs_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -236,3 +236,11 @@ func TestBlobsIndex(t *testing.T) {
236236
)},
237237
)
238238
}
239+
240+
func TestBlobsIndexIterClosed(t *testing.T) {
241+
testTableIndexIterClosed(t, new(blobsTable))
242+
}
243+
244+
func TestBlobsIterClosed(t *testing.T) {
245+
testTableIterClosed(t, new(blobsTable))
246+
}

commit_blobs_test.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,14 @@
11
package gitbase
22

33
import (
4+
"io"
45
"testing"
56

67
"github.com/stretchr/testify/require"
78
"gopkg.in/src-d/go-git.v4/plumbing"
89
"gopkg.in/src-d/go-mysql-server.v0/sql"
910
"gopkg.in/src-d/go-mysql-server.v0/sql/expression"
11+
"gopkg.in/src-d/go-mysql-server.v0/sql/plan"
1012
)
1113

1214
func TestCommitBlobsTableRowIter(t *testing.T) {
@@ -202,3 +204,29 @@ func TestCommitBlobsRowKeyMapper(t *testing.T) {
202204

203205
require.Equal(row, row2)
204206
}
207+
208+
func TestCommitBlobsIndexIterClosed(t *testing.T) {
209+
testTableIndexIterClosed(t, new(commitBlobsTable))
210+
}
211+
212+
// This one is not using testTableIterClosed as it takes too much time
213+
// to go through all the rows. Here we limit it to the first 100.
214+
func TestCommitBlobsIterClosed(t *testing.T) {
215+
require := require.New(t)
216+
ctx, closed := setupSivaCloseRepos(t, "_testdata")
217+
218+
table := new(commitBlobsTable)
219+
iter, err := plan.NewResolvedTable(table).RowIter(ctx)
220+
require.NoError(err)
221+
222+
for i := 0; i < 100; i++ {
223+
_, err = iter.Next()
224+
if err != nil {
225+
require.Equal(io.EOF, err)
226+
break
227+
}
228+
}
229+
230+
iter.Close()
231+
require.True(closed.Check())
232+
}

commit_files.go

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -301,6 +301,10 @@ func (i *commitFilesRowIter) Close() error {
301301
i.files.Close()
302302
}
303303

304+
if i.repo != nil {
305+
i.repo.Close()
306+
}
307+
304308
if i.index != nil {
305309
return i.index.Close()
306310
}
@@ -488,6 +492,14 @@ func (i *commitFilesKeyValueIter) Close() error {
488492
i.files.Close()
489493
}
490494

495+
if i.idx != nil {
496+
i.idx.Close()
497+
}
498+
499+
if i.repo != nil {
500+
i.repo.Close()
501+
}
502+
491503
return nil
492504
}
493505

commit_files_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -98,3 +98,11 @@ func TestEncodeCommitFileIndexKey(t *testing.T) {
9898

9999
require.Equal(k, k2)
100100
}
101+
102+
func TestCommitFilesIndexIterClosed(t *testing.T) {
103+
testTableIndexIterClosed(t, new(commitFilesTable))
104+
}
105+
106+
func TestCommitFilesIterClosed(t *testing.T) {
107+
testTableIterClosed(t, new(commitFilesTable))
108+
}

commit_trees_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -168,3 +168,11 @@ func TestCommitTreesRowKeyMapper(t *testing.T) {
168168

169169
require.Equal(row, row2)
170170
}
171+
172+
func TestCommitTreesIndexIterClosed(t *testing.T) {
173+
testTableIndexIterClosed(t, new(commitTreesTable))
174+
}
175+
176+
func TestCommitTreesIterClosed(t *testing.T) {
177+
testTableIterClosed(t, new(commitTreesTable))
178+
}

commits.go

Lines changed: 16 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -190,7 +190,9 @@ func (i *commitIter) Close() error {
190190
i.iter.Close()
191191
}
192192

193-
i.repo.Close()
193+
if i.repo != nil {
194+
i.repo.Close()
195+
}
194196

195197
return nil
196198
}
@@ -275,6 +277,10 @@ func (i *commitsByHashIter) Close() {
275277
if i.commitIter != nil {
276278
i.commitIter.Close()
277279
}
280+
281+
if i.repo != nil {
282+
i.repo.Close()
283+
}
278284
}
279285

280286
func (i *commitsByHashIter) nextScan() (*object.Commit, error) {
@@ -374,6 +380,15 @@ func (i *commitsKeyValueIter) Close() error {
374380
if i.commits != nil {
375381
i.commits.Close()
376382
}
383+
384+
if i.idx != nil {
385+
i.idx.Close()
386+
}
387+
388+
if i.repo != nil {
389+
i.repo.Close()
390+
}
391+
377392
return nil
378393
}
379394

commits_test.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -298,3 +298,11 @@ func TestCommitsIndex(t *testing.T) {
298298
)},
299299
)
300300
}
301+
302+
func TestCommitsIndexIterClosed(t *testing.T) {
303+
testTableIndexIterClosed(t, new(commitsTable))
304+
}
305+
306+
func TestCommitsIterClosed(t *testing.T) {
307+
testTableIterClosed(t, new(commitsTable))
308+
}

common_test.go

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,11 +2,22 @@ package gitbase
22

33
import (
44
"context"
5+
"io"
6+
"io/ioutil"
7+
"os"
8+
"path/filepath"
9+
"strings"
10+
"sync"
511
"testing"
612

713
"github.com/stretchr/testify/require"
14+
"gopkg.in/src-d/go-billy-siva.v4"
15+
billy "gopkg.in/src-d/go-billy.v4"
16+
"gopkg.in/src-d/go-billy.v4/osfs"
817
fixtures "gopkg.in/src-d/go-git-fixtures.v3"
18+
git "gopkg.in/src-d/go-git.v4"
919
"gopkg.in/src-d/go-git.v4/plumbing/cache"
20+
"gopkg.in/src-d/go-git.v4/storage/filesystem"
1021
"gopkg.in/src-d/go-mysql-server.v0/sql"
1122
"gopkg.in/src-d/go-mysql-server.v0/sql/plan"
1223
)
@@ -65,3 +76,180 @@ func buildSession(t *testing.T, repos fixtures.Fixtures,
6576
func tableToRows(ctx *sql.Context, t sql.Table) ([]sql.Row, error) {
6677
return sql.NodeToRows(ctx, plan.NewResolvedTable(t))
6778
}
79+
80+
/*
81+
82+
The following code adds utilities to test that siva files are properly closed.
83+
Instead of using normal setup you can use setupSivaCloseRepos, it returns
84+
a context with a pool with all the sivas in "_testdata" directory. It also
85+
tracks all siva filesystems opened. Its closed state can be checked with
86+
closedSiva.Check().
87+
88+
*/
89+
90+
type closedSiva struct {
91+
closed []bool
92+
m sync.Mutex
93+
}
94+
95+
func (c *closedSiva) NewFS(path string) (billy.Filesystem, error) {
96+
c.m.Lock()
97+
defer c.m.Unlock()
98+
99+
localfs := osfs.New(filepath.Dir(path))
100+
101+
tmpDir, err := ioutil.TempDir(os.TempDir(), "gitbase-siva")
102+
if err != nil {
103+
return nil, err
104+
}
105+
106+
tmpfs := osfs.New(tmpDir)
107+
108+
fs, err := sivafs.NewFilesystem(localfs, filepath.Base(path), tmpfs)
109+
if err != nil {
110+
return nil, err
111+
}
112+
113+
pos := len(c.closed)
114+
c.closed = append(c.closed, false)
115+
116+
fun := func() {
117+
c.m.Lock()
118+
defer c.m.Unlock()
119+
c.closed[pos] = true
120+
}
121+
122+
return &closedSivaFilesystem{fs, fun}, nil
123+
}
124+
125+
func (c *closedSiva) Check() bool {
126+
for _, f := range c.closed {
127+
if !f {
128+
return false
129+
}
130+
}
131+
132+
return true
133+
}
134+
135+
type closedSivaFilesystem struct {
136+
sivafs.SivaFS
137+
closeFunc func()
138+
}
139+
140+
func (c *closedSivaFilesystem) Sync() error {
141+
if c.closeFunc != nil {
142+
c.closeFunc()
143+
}
144+
145+
return c.SivaFS.Sync()
146+
}
147+
148+
var _ repository = new(closedSivaRepository)
149+
150+
type closedSivaRepository struct {
151+
path string
152+
siva *closedSiva
153+
cache cache.Object
154+
}
155+
156+
func (c *closedSivaRepository) ID() string {
157+
return c.path
158+
}
159+
160+
func (c *closedSivaRepository) Repo() (*Repository, error) {
161+
fs, err := c.FS()
162+
if err != nil {
163+
return nil, err
164+
}
165+
166+
s := fs.(*closedSivaFilesystem)
167+
closeFunc := func() { s.Sync() }
168+
169+
sto := filesystem.NewStorageWithOptions(fs, c.Cache(), gitStorerOptions)
170+
repo, err := git.Open(sto, nil)
171+
if err != nil {
172+
return nil, err
173+
174+
}
175+
176+
return NewRepository(c.path, repo, closeFunc), nil
177+
}
178+
179+
func (c *closedSivaRepository) FS() (billy.Filesystem, error) {
180+
return c.siva.NewFS(c.path)
181+
}
182+
183+
func (c *closedSivaRepository) Path() string {
184+
return c.path
185+
}
186+
187+
func (c *closedSivaRepository) Cache() cache.Object {
188+
if c.cache == nil {
189+
c.cache = cache.NewObjectLRUDefault()
190+
}
191+
192+
return c.cache
193+
}
194+
195+
// setupSivaCloseRepos creates a pool with siva files that can be checked
196+
// if they've been closed.
197+
func setupSivaCloseRepos(t *testing.T, dir string) (*sql.Context, *closedSiva) {
198+
require := require.New(t)
199+
200+
t.Helper()
201+
202+
cs := new(closedSiva)
203+
pool := NewRepositoryPool(cache.DefaultMaxSize)
204+
205+
filepath.Walk(dir,
206+
func(path string, info os.FileInfo, err error) error {
207+
if strings.HasSuffix(path, ".siva") {
208+
repo := &closedSivaRepository{path: path, siva: cs}
209+
err := pool.Add(repo)
210+
require.NoError(err)
211+
}
212+
213+
return nil
214+
},
215+
)
216+
217+
session := NewSession(pool, WithSkipGitErrors(true))
218+
ctx := sql.NewContext(context.TODO(), sql.WithSession(session))
219+
220+
return ctx, cs
221+
}
222+
223+
func testTableIndexIterClosed(t *testing.T, table sql.IndexableTable) {
224+
t.Helper()
225+
226+
require := require.New(t)
227+
ctx, closed := setupSivaCloseRepos(t, "_testdata")
228+
229+
iter, err := table.IndexKeyValues(ctx, nil)
230+
require.NoError(err)
231+
232+
for {
233+
_, i, err := iter.Next()
234+
if err != nil {
235+
require.Equal(io.EOF, err)
236+
break
237+
}
238+
239+
i.Close()
240+
}
241+
242+
iter.Close()
243+
require.True(closed.Check())
244+
}
245+
246+
func testTableIterClosed(t *testing.T, table sql.IndexableTable) {
247+
t.Helper()
248+
249+
require := require.New(t)
250+
ctx, closed := setupSivaCloseRepos(t, "_testdata")
251+
_, err := tableToRows(ctx, table)
252+
require.NoError(err)
253+
254+
require.True(closed.Check())
255+
}

files.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -492,6 +492,14 @@ func (i *filesKeyValueIter) Close() error {
492492
i.files.Close()
493493
}
494494

495+
if i.idx != nil {
496+
i.idx.Close()
497+
}
498+
499+
if i.repo != nil {
500+
i.repo.Close()
501+
}
502+
495503
return nil
496504
}
497505

0 commit comments

Comments
 (0)