@cryptotaxi247 / kubo / commits / c8992f2c1

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 +}