master
go 72 lines 1.29 KB
Raw
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 }