Skip to content
This repository was archived by the owner on Sep 11, 2020. It is now read-only.

Commit eb243ba

Browse files
authored
Merge pull request #1123 from saracen/object-storage-open-packfile
filesystem: ObjectStorage, MaxOpenDescriptors option
2 parents 7b3220f + f5c23da commit eb243ba

File tree

5 files changed

+143
-49
lines changed

5 files changed

+143
-49
lines changed

plumbing/format/packfile/packfile.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -414,6 +414,11 @@ func (p *Packfile) ID() (plumbing.Hash, error) {
414414
return hash, nil
415415
}
416416

417+
// Scanner returns the packfile's Scanner
418+
func (p *Packfile) Scanner() *Scanner {
419+
return p.s
420+
}
421+
417422
// Close the packfile and its resources.
418423
func (p *Packfile) Close() error {
419424
closer, ok := p.file.(io.Closer)

storage/filesystem/dotgit/dotgit.go

Lines changed: 11 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -83,7 +83,7 @@ type DotGit struct {
8383
packList []plumbing.Hash
8484
packMap map[plumbing.Hash]struct{}
8585

86-
files map[string]billy.File
86+
files map[plumbing.Hash]billy.File
8787
}
8888

8989
// New returns a DotGit value ready to be used. The path argument must
@@ -245,8 +245,15 @@ func (d *DotGit) objectPackPath(hash plumbing.Hash, extension string) string {
245245
}
246246

247247
func (d *DotGit) objectPackOpen(hash plumbing.Hash, extension string) (billy.File, error) {
248-
if d.files == nil {
249-
d.files = make(map[string]billy.File)
248+
if d.options.KeepDescriptors && extension == "pack" {
249+
if d.files == nil {
250+
d.files = make(map[plumbing.Hash]billy.File)
251+
}
252+
253+
f, ok := d.files[hash]
254+
if ok {
255+
return f, nil
256+
}
250257
}
251258

252259
err := d.hasPack(hash)
@@ -255,11 +262,6 @@ func (d *DotGit) objectPackOpen(hash plumbing.Hash, extension string) (billy.Fil
255262
}
256263

257264
path := d.objectPackPath(hash, extension)
258-
f, ok := d.files[path]
259-
if ok {
260-
return f, nil
261-
}
262-
263265
pack, err := d.fs.Open(path)
264266
if err != nil {
265267
if os.IsNotExist(err) {
@@ -270,7 +272,7 @@ func (d *DotGit) objectPackOpen(hash plumbing.Hash, extension string) (billy.Fil
270272
}
271273

272274
if d.options.KeepDescriptors && extension == "pack" {
273-
d.files[path] = pack
275+
d.files[hash] = pack
274276
}
275277

276278
return pack, nil

storage/filesystem/object.go

Lines changed: 106 additions & 39 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ type ObjectStorage struct {
2626

2727
dir *dotgit.DotGit
2828
index map[plumbing.Hash]idxfile.Index
29+
30+
packList []plumbing.Hash
31+
packListIdx int
32+
packfiles map[plumbing.Hash]*packfile.Packfile
2933
}
3034

3135
// NewObjectStorage creates a new ObjectStorage with the given .git directory and cache.
@@ -187,6 +191,73 @@ func (s *ObjectStorage) encodedObjectSizeFromUnpacked(h plumbing.Hash) (
187191
return size, err
188192
}
189193

194+
func (s *ObjectStorage) packfile(idx idxfile.Index, pack plumbing.Hash) (*packfile.Packfile, error) {
195+
if p := s.packfileFromCache(pack); p != nil {
196+
return p, nil
197+
}
198+
199+
f, err := s.dir.ObjectPack(pack)
200+
if err != nil {
201+
return nil, err
202+
}
203+
204+
var p *packfile.Packfile
205+
if s.objectCache != nil {
206+
p = packfile.NewPackfileWithCache(idx, s.dir.Fs(), f, s.objectCache)
207+
} else {
208+
p = packfile.NewPackfile(idx, s.dir.Fs(), f)
209+
}
210+
211+
return p, s.storePackfileInCache(pack, p)
212+
}
213+
214+
func (s *ObjectStorage) packfileFromCache(hash plumbing.Hash) *packfile.Packfile {
215+
if s.packfiles == nil {
216+
if s.options.KeepDescriptors {
217+
s.packfiles = make(map[plumbing.Hash]*packfile.Packfile)
218+
} else if s.options.MaxOpenDescriptors > 0 {
219+
s.packList = make([]plumbing.Hash, s.options.MaxOpenDescriptors)
220+
s.packfiles = make(map[plumbing.Hash]*packfile.Packfile, s.options.MaxOpenDescriptors)
221+
}
222+
}
223+
224+
return s.packfiles[hash]
225+
}
226+
227+
func (s *ObjectStorage) storePackfileInCache(hash plumbing.Hash, p *packfile.Packfile) error {
228+
if s.options.KeepDescriptors {
229+
s.packfiles[hash] = p
230+
return nil
231+
}
232+
233+
if s.options.MaxOpenDescriptors <= 0 {
234+
return nil
235+
}
236+
237+
// start over as the limit of packList is hit
238+
if s.packListIdx >= len(s.packList) {
239+
s.packListIdx = 0
240+
}
241+
242+
// close the existing packfile if open
243+
if next := s.packList[s.packListIdx]; !next.IsZero() {
244+
open := s.packfiles[next]
245+
delete(s.packfiles, next)
246+
if open != nil {
247+
if err := open.Close(); err != nil {
248+
return err
249+
}
250+
}
251+
}
252+
253+
// cache newly open packfile
254+
s.packList[s.packListIdx] = hash
255+
s.packfiles[hash] = p
256+
s.packListIdx++
257+
258+
return nil
259+
}
260+
190261
func (s *ObjectStorage) encodedObjectSizeFromPackfile(h plumbing.Hash) (
191262
size int64, err error) {
192263
if err := s.requireIndex(); err != nil {
@@ -198,12 +269,6 @@ func (s *ObjectStorage) encodedObjectSizeFromPackfile(h plumbing.Hash) (
198269
return 0, plumbing.ErrObjectNotFound
199270
}
200271

201-
f, err := s.dir.ObjectPack(pack)
202-
if err != nil {
203-
return 0, err
204-
}
205-
defer ioutil.CheckClose(f, &err)
206-
207272
idx := s.index[pack]
208273
hash, err := idx.FindHash(offset)
209274
if err == nil {
@@ -215,11 +280,13 @@ func (s *ObjectStorage) encodedObjectSizeFromPackfile(h plumbing.Hash) (
215280
return 0, err
216281
}
217282

218-
var p *packfile.Packfile
219-
if s.objectCache != nil {
220-
p = packfile.NewPackfileWithCache(idx, s.dir.Fs(), f, s.objectCache)
221-
} else {
222-
p = packfile.NewPackfile(idx, s.dir.Fs(), f)
283+
p, err := s.packfile(idx, pack)
284+
if err != nil {
285+
return 0, err
286+
}
287+
288+
if !s.options.KeepDescriptors && s.options.MaxOpenDescriptors == 0 {
289+
defer ioutil.CheckClose(p, &err)
223290
}
224291

225292
return p.GetSizeByOffset(offset)
@@ -361,29 +428,28 @@ func (s *ObjectStorage) getFromPackfile(h plumbing.Hash, canBeDelta bool) (
361428
return nil, plumbing.ErrObjectNotFound
362429
}
363430

364-
f, err := s.dir.ObjectPack(pack)
431+
idx := s.index[pack]
432+
p, err := s.packfile(idx, pack)
365433
if err != nil {
366434
return nil, err
367435
}
368436

369-
if !s.options.KeepDescriptors {
370-
defer ioutil.CheckClose(f, &err)
437+
if !s.options.KeepDescriptors && s.options.MaxOpenDescriptors == 0 {
438+
defer ioutil.CheckClose(p, &err)
371439
}
372440

373-
idx := s.index[pack]
374441
if canBeDelta {
375-
return s.decodeDeltaObjectAt(f, idx, offset, hash)
442+
return s.decodeDeltaObjectAt(p, offset, hash)
376443
}
377444

378-
return s.decodeObjectAt(f, idx, offset)
445+
return s.decodeObjectAt(p, offset)
379446
}
380447

381448
func (s *ObjectStorage) decodeObjectAt(
382-
f billy.File,
383-
idx idxfile.Index,
449+
p *packfile.Packfile,
384450
offset int64,
385451
) (plumbing.EncodedObject, error) {
386-
hash, err := idx.FindHash(offset)
452+
hash, err := p.FindHash(offset)
387453
if err == nil {
388454
obj, ok := s.objectCache.Get(hash)
389455
if ok {
@@ -395,28 +461,16 @@ func (s *ObjectStorage) decodeObjectAt(
395461
return nil, err
396462
}
397463

398-
var p *packfile.Packfile
399-
if s.objectCache != nil {
400-
p = packfile.NewPackfileWithCache(idx, s.dir.Fs(), f, s.objectCache)
401-
} else {
402-
p = packfile.NewPackfile(idx, s.dir.Fs(), f)
403-
}
404-
405464
return p.GetByOffset(offset)
406465
}
407466

408467
func (s *ObjectStorage) decodeDeltaObjectAt(
409-
f billy.File,
410-
idx idxfile.Index,
468+
p *packfile.Packfile,
411469
offset int64,
412470
hash plumbing.Hash,
413471
) (plumbing.EncodedObject, error) {
414-
if _, err := f.Seek(0, io.SeekStart); err != nil {
415-
return nil, err
416-
}
417-
418-
p := packfile.NewScanner(f)
419-
header, err := p.SeekObjectHeader(offset)
472+
scan := p.Scanner()
473+
header, err := scan.SeekObjectHeader(offset)
420474
if err != nil {
421475
return nil, err
422476
}
@@ -429,12 +483,12 @@ func (s *ObjectStorage) decodeDeltaObjectAt(
429483
case plumbing.REFDeltaObject:
430484
base = header.Reference
431485
case plumbing.OFSDeltaObject:
432-
base, err = idx.FindHash(header.OffsetReference)
486+
base, err = p.FindHash(header.OffsetReference)
433487
if err != nil {
434488
return nil, err
435489
}
436490
default:
437-
return s.decodeObjectAt(f, idx, offset)
491+
return s.decodeObjectAt(p, offset)
438492
}
439493

440494
obj := &plumbing.MemoryObject{}
@@ -444,7 +498,7 @@ func (s *ObjectStorage) decodeDeltaObjectAt(
444498
return nil, err
445499
}
446500

447-
if _, _, err := p.NextObject(w); err != nil {
501+
if _, _, err := scan.NextObject(w); err != nil {
448502
return nil, err
449503
}
450504

@@ -515,7 +569,20 @@ func (s *ObjectStorage) buildPackfileIters(
515569

516570
// Close closes all opened files.
517571
func (s *ObjectStorage) Close() error {
518-
return s.dir.Close()
572+
var firstError error
573+
if s.options.KeepDescriptors || s.options.MaxOpenDescriptors > 0 {
574+
for _, packfile := range s.packfiles {
575+
err := packfile.Close()
576+
if firstError == nil && err != nil {
577+
firstError = err
578+
}
579+
}
580+
}
581+
582+
s.packfiles = nil
583+
s.dir.Close()
584+
585+
return firstError
519586
}
520587

521588
type lazyPackfilesIter struct {

storage/filesystem/object_test.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -86,6 +86,24 @@ func (s *FsSuite) TestGetFromPackfileKeepDescriptors(c *C) {
8686
})
8787
}
8888

89+
func (s *FsSuite) TestGetFromPackfileMaxOpenDescriptors(c *C) {
90+
fs := fixtures.ByTag(".git").ByTag("multi-packfile").One().DotGit()
91+
o := NewObjectStorageWithOptions(dotgit.New(fs), cache.NewObjectLRUDefault(), Options{MaxOpenDescriptors: 1})
92+
93+
expected := plumbing.NewHash("8d45a34641d73851e01d3754320b33bb5be3c4d3")
94+
obj, err := o.getFromPackfile(expected, false)
95+
c.Assert(err, IsNil)
96+
c.Assert(obj.Hash(), Equals, expected)
97+
98+
expected = plumbing.NewHash("e9cfa4c9ca160546efd7e8582ec77952a27b17db")
99+
obj, err = o.getFromPackfile(expected, false)
100+
c.Assert(err, IsNil)
101+
c.Assert(obj.Hash(), Equals, expected)
102+
103+
err = o.Close()
104+
c.Assert(err, IsNil)
105+
}
106+
89107
func (s *FsSuite) TestGetSizeOfObjectFile(c *C) {
90108
fs := fixtures.ByTag(".git").ByTag("unpacked").One().DotGit()
91109
o := NewObjectStorage(dotgit.New(fs), cache.NewObjectLRUDefault())

storage/filesystem/storage.go

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,9 @@ type Options struct {
3131
// KeepDescriptors makes the file descriptors to be reused but they will
3232
// need to be manually closed calling Close().
3333
KeepDescriptors bool
34+
// MaxOpenDescriptors is the max number of file descriptors to keep
35+
// open. If KeepDescriptors is true, all file descriptors will remain open.
36+
MaxOpenDescriptors int
3437
}
3538

3639
// NewStorage returns a new Storage backed by a given `fs.Filesystem` and cache.
@@ -43,7 +46,6 @@ func NewStorage(fs billy.Filesystem, cache cache.Object) *Storage {
4346
func NewStorageWithOptions(fs billy.Filesystem, cache cache.Object, ops Options) *Storage {
4447
dirOps := dotgit.Options{
4548
ExclusiveAccess: ops.ExclusiveAccess,
46-
KeepDescriptors: ops.KeepDescriptors,
4749
}
4850
dir := dotgit.NewWithOptions(fs, dirOps)
4951

0 commit comments

Comments
 (0)