Commit c8992f2c authored by Tommi Virtanen's avatar Tommi Virtanen

repo.OnlyOne tracks open Repos and reuses them

This will replace the elaborate refcounting in fsrepo, to make it
easier to maintain.
parent fdd1cd8d
......@@ -50,6 +50,20 @@ var (
dsLock sync.Mutex
dsOpenersCounter *counter.Openers
datastores map[string]ds2.ThreadSafeDatastoreCloser
// onlyOne keeps track of open FSRepo instances.
//
// TODO: once command Context / Repo integration is cleaned up,
// this can be removed. Right now, this makes ConfigCmd.Run
// function try to open the repo twice:
//
// $ ipfs daemon &
// $ ipfs config foo
//
// The reason for the above is that in standalone mode without the
// daemon, `ipfs config` tries to save work by not building the
// full IpfsNode, but accessing the Repo directly.
onlyOne repo.OnlyOne
)
func init() {
......@@ -77,7 +91,14 @@ var _ repo.Repo = (*FSRepo)(nil)
// Open the FSRepo at path. Returns an error if the repo is not
// initialized.
func Open(repoPath string) (*FSRepo, error) {
func Open(repoPath string) (repo.Repo, error) {
fn := func() (repo.Repo, error) {
return open(repoPath)
}
return onlyOne.Open(repoPath, fn)
}
func open(repoPath string) (repo.Repo, error) {
packageLock.Lock()
defer packageLock.Unlock()
......
......@@ -111,7 +111,7 @@ func TestOpenMoreThanOnceInSameProcess(t *testing.T) {
assert.Nil(err, t, "first repo should open successfully")
r2, err := Open(path)
assert.Nil(err, t, "second repo should open successfully")
assert.True(r1.ds == r2.ds, t, "repos should share the datastore")
assert.True(r1 == r2, t, "second open returns same value")
assert.Nil(r1.Close(), t)
assert.Nil(r2.Close(), t)
......
package repo
import (
"sync"
)
// OnlyOne tracks open Repos by arbitrary key and returns the already
// open one.
type OnlyOne struct {
mu sync.Mutex
active map[interface{}]*ref
}
// Open a Repo identified by key. If Repo is not already open, the
// open function is called, and the result is remember for further
// use.
//
// Key must be comparable, or Open will panic. Make sure to pick keys
// that are unique across different concrete Repo implementations,
// e.g. by creating a local type:
//
// type repoKey string
// r, err := o.Open(repoKey(path), open)
//
// Call Repo.Close when done.
func (o *OnlyOne) Open(key interface{}, open func() (Repo, error)) (Repo, error) {
o.mu.Lock()
defer o.mu.Unlock()
if o.active == nil {
o.active = make(map[interface{}]*ref)
}
item, found := o.active[key]
if !found {
repo, err := open()
if err != nil {
return nil, err
}
item = &ref{
parent: o,
key: key,
Repo: repo,
}
o.active[key] = item
}
item.refs++
return item, nil
}
type ref struct {
parent *OnlyOne
key interface{}
refs uint32
Repo
}
var _ Repo = (*ref)(nil)
func (r *ref) Close() error {
r.parent.mu.Lock()
defer r.parent.mu.Unlock()
r.refs--
if r.refs > 0 {
// others are holding it open
return nil
}
// last one
delete(r.parent.active, r.key)
return r.Repo.Close()
}
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment