/ repo / onlyone.go
onlyone.go
 1  package repo
 2  
 3  import (
 4  	"sync"
 5  )
 6  
 7  // OnlyOne tracks open Repos by arbitrary key and returns the already
 8  // open one.
 9  type OnlyOne struct {
10  	mu     sync.Mutex
11  	active map[any]*ref
12  }
13  
14  // Open a Repo identified by key. If Repo is not already open, the
15  // open function is called, and the result is remembered for further
16  // use.
17  //
18  // Key must be comparable, or Open will panic. Make sure to pick keys
19  // that are unique across different concrete Repo implementations,
20  // e.g. by creating a local type:
21  //
22  //	type repoKey string
23  //	r, err := o.Open(repoKey(path), open)
24  //
25  // Call Repo.Close when done.
26  func (o *OnlyOne) Open(key any, open func() (Repo, error)) (Repo, error) {
27  	o.mu.Lock()
28  	defer o.mu.Unlock()
29  	if o.active == nil {
30  		o.active = make(map[any]*ref)
31  	}
32  
33  	item, found := o.active[key]
34  	if !found {
35  		repo, err := open()
36  		if err != nil {
37  			return nil, err
38  		}
39  		item = &ref{
40  			parent: o,
41  			key:    key,
42  			Repo:   repo,
43  		}
44  		o.active[key] = item
45  	}
46  	item.refs++
47  	return item, nil
48  }
49  
50  type ref struct {
51  	parent *OnlyOne
52  	key    any
53  	refs   uint32
54  	Repo
55  }
56  
57  var _ Repo = (*ref)(nil)
58  
59  func (r *ref) Close() error {
60  	r.parent.mu.Lock()
61  	defer r.parent.mu.Unlock()
62  
63  	r.refs--
64  	if r.refs > 0 {
65  		// others are holding it open
66  		return nil
67  	}
68  
69  	// last one
70  	delete(r.parent.active, r.key)
71  	return r.Repo.Close()
72  }