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:
@ -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)
|
||||
|
||||
72
repo/onlyone.go
Normal file
72
repo/onlyone.go
Normal 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()
|
||||
}
|
||||
Reference in New Issue
Block a user