1
0
mirror of https://github.com/ipfs/kubo.git synced 2025-10-24 15:12:55 +08:00

repo.OnlyOne tracks open Repos and reuses them

This will replace the elaborate refcounting in fsrepo, to make it
easier to maintain.
This commit is contained in:
Tommi Virtanen
2015-03-13 19:05:06 -07:00
parent fdd1cd8dc0
commit c8992f2c1b
3 changed files with 95 additions and 2 deletions

View File

@ -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()

View File

@ -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)

72
repo/onlyone.go Normal file
View File

@ -0,0 +1,72 @@
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()
}