repo.OnlyOne tracks open Repos and reuses them
This will replace the elaborate refcounting in fsrepo, to make it easier to maintain.
Tommi Virtanen committed
Mar 13, 2015 at 19:05 UTC
c8992f2c1b792b96eba12de9648d194e02938a75
3 files changed
+95
-2
repo/fsrepo/fsrepo.go
+22
-1
@@ -50,6 +50,20 @@ var (
50
dsLock sync.Mutex
51
dsOpenersCounter *counter.Openers
52
datastores map[string]ds2.ThreadSafeDatastoreCloser
53
+
54
+ // onlyOne keeps track of open FSRepo instances.
55
+ //
56
+ // TODO: once command Context / Repo integration is cleaned up,
57
+ // this can be removed. Right now, this makes ConfigCmd.Run
58
+ // function try to open the repo twice:
59
+ //
60
+ // $ ipfs daemon &
61
+ // $ ipfs config foo
62
+ //
63
+ // The reason for the above is that in standalone mode without the
64
+ // daemon, `ipfs config` tries to save work by not building the
65
+ // full IpfsNode, but accessing the Repo directly.
66
+ onlyOne repo.OnlyOne
67
)
68
69
func init() {
@@ -77,7 +91,14 @@ var _ repo.Repo = (*FSRepo)(nil)
91
92
// Open the FSRepo at path. Returns an error if the repo is not
93
// initialized.
80
-func Open(repoPath string) (*FSRepo, error) {
94
+func Open(repoPath string) (repo.Repo, error) {
95
+ fn := func() (repo.Repo, error) {
96
+ return open(repoPath)
97
+ }
98
+ return onlyOne.Open(repoPath, fn)
99
+}
100
+
101
+func open(repoPath string) (repo.Repo, error) {
102
packageLock.Lock()
103
defer packageLock.Unlock()
104
repo/fsrepo/fsrepo_test.go
+1
-1
@@ -111,7 +111,7 @@ func TestOpenMoreThanOnceInSameProcess(t *testing.T) {
111
assert.Nil(err, t, "first repo should open successfully")
112
r2, err := Open(path)
113
assert.Nil(err, t, "second repo should open successfully")
114
- assert.True(r1.ds == r2.ds, t, "repos should share the datastore")
114
+ assert.True(r1 == r2, t, "second open returns same value")
115
116
assert.Nil(r1.Close(), t)
117
assert.Nil(r2.Close(), t)
repo/onlyone.go
new
+72
@@ -0,0 +1,72 @@
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[interface{}]*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 remember 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 interface{}, 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[interface{}]*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 interface{}
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
+}