@cryptotaxi247 / kubo / commits / 40e41d24f

feat(fsrepo): protect with a repo lockfile

NB: daemon is one spot the repo lock is typically acquired

Brian Tiger Chow committed Jan 13, 2015 at 17:19 UTC 40e41d24f7fe57d33e3d73107653a1c50f4340c7
5 files changed +60 -16
cmd/ipfs/daemon.go
+5 -5
@@ -12,7 +12,7 @@ import (
12 cmdsHttp "github.com/jbenet/go-ipfs/commands/http"
13 core "github.com/jbenet/go-ipfs/core"
14 commands "github.com/jbenet/go-ipfs/core/commands"
15 - daemon "github.com/jbenet/go-ipfs/core/daemon"
15 + fsrepo "github.com/jbenet/go-ipfs/repo/fsrepo"
16 util "github.com/jbenet/go-ipfs/util"
17 "github.com/jbenet/go-ipfs/util/debugerror"
18 )
@@ -89,13 +89,13 @@ func daemonFunc(req cmds.Request) (interface{}, error) {
89 return nil, err
90 }
91
92 - // acquire the daemon lock _before_ constructing a node. we need to make
92 + // acquire the repo lock _before_ constructing a node. we need to make
93 // sure we are permitted to access the resources (datastore, etc.)
94 - lock, err := daemon.Lock(req.Context().ConfigRoot)
95 - if err != nil {
94 + repo := fsrepo.At(req.Context().ConfigRoot)
95 + if err := repo.Open(); err != nil {
96 return nil, debugerror.Errorf("Couldn't obtain lock. Is another daemon already running?")
97 }
98 - defer lock.Close()
98 + defer repo.Close()
99
100 // OK!!! Now we're ready to construct the node.
101 // make sure we construct an online node.
cmd/ipfs/main.go
+1 -2
@@ -20,7 +20,6 @@ import (
20 cmdsCli "github.com/jbenet/go-ipfs/commands/cli"
21 cmdsHttp "github.com/jbenet/go-ipfs/commands/http"
22 core "github.com/jbenet/go-ipfs/core"
23 - daemon "github.com/jbenet/go-ipfs/core/daemon"
23 repo "github.com/jbenet/go-ipfs/repo"
24 config "github.com/jbenet/go-ipfs/repo/config"
25 fsrepo "github.com/jbenet/go-ipfs/repo/fsrepo"
@@ -392,7 +391,7 @@ func commandShouldRunOnDaemon(details cmdDetails, req cmds.Request, root *cmds.C
391
392 // at this point need to know whether daemon is running. we defer
393 // to this point so that some commands dont open files unnecessarily.
395 - daemonLocked := daemon.Locked(req.Context().ConfigRoot)
394 + daemonLocked := fsrepo.LockedByOtherProcess(req.Context().ConfigRoot)
395
396 if daemonLocked {
397
repo/fsrepo/fsrepo.go
+48 -5
@@ -10,6 +10,7 @@ import (
10 repo "github.com/jbenet/go-ipfs/repo"
11 common "github.com/jbenet/go-ipfs/repo/common"
12 config "github.com/jbenet/go-ipfs/repo/config"
13 + lockfile "github.com/jbenet/go-ipfs/repo/fsrepo/lock"
14 opener "github.com/jbenet/go-ipfs/repo/fsrepo/opener"
15 util "github.com/jbenet/go-ipfs/util"
16 debugerror "github.com/jbenet/go-ipfs/util/debugerror"
@@ -24,10 +25,13 @@ var (
25 // If an operation is used when repo is Open and the operation does not
26 // change the repo's state, the package lock does not need to be acquired.
27 openerCounter *opener.Counter
28 +
29 + lockfiles map[string]io.Closer
30 )
31
32 func init() {
33 openerCounter = opener.NewCounter()
34 + lockfiles = make(map[string]io.Closer)
35 }
36
37 // FSRepo represents an IPFS FileSystem Repo. It is not thread-safe.
@@ -74,6 +78,15 @@ func Remove(path string) error {
78 return os.RemoveAll(path)
79 }
80
81 +// LockedByOtherProcess returns true if the FSRepo is locked by another
82 +// process. If true, then the repo cannot be opened by this process.
83 +func LockedByOtherProcess(repoPath string) bool {
84 + openerCounter.Lock()
85 + defer openerCounter.Unlock()
86 + // NB: the lock is only held when repos are Open
87 + return lockfile.Locked(repoPath) && openerCounter.NumOpeners(repoPath) == 0
88 +}
89 +
90 // Open returns an error if the repo is not initialized.
91 func (r *FSRepo) Open() error {
92 openerCounter.Lock()
@@ -118,9 +131,7 @@ func (r *FSRepo) Open() error {
131 return debugerror.Errorf("logs: %s", err)
132 }
133
121 - r.state = opened
122 - openerCounter.AddOpener(r.path)
123 - return nil
134 + return transitionToOpened(r)
135 }
136
137 // Config returns the FSRepo's config. This method must not be called if the
@@ -217,8 +228,7 @@ func (r *FSRepo) Close() error {
228 if r.state != opened {
229 return debugerror.Errorf("repo is %s", r.state)
230 }
220 - openerCounter.RemoveOpener(r.path)
221 - return nil // TODO release repo lock
231 + return transitionToClosed(r)
232 }
233
234 var _ io.Closer = &FSRepo{}
@@ -258,3 +268,36 @@ func initCheckDir(path string) error {
268 }
269 return nil
270 }
271 +
272 +// transitionToOpened manages the state transition to |opened|. Caller must hold
273 +// openerCounter lock.
274 +func transitionToOpened(r *FSRepo) error {
275 + r.state = opened
276 + if countBefore := openerCounter.NumOpeners(r.path); countBefore == 0 { // #first
277 + closer, err := lockfile.Lock(r.path)
278 + if err != nil {
279 + return err
280 + }
281 + lockfiles[r.path] = closer
282 + }
283 + return openerCounter.AddOpener(r.path)
284 +}
285 +
286 +// transitionToClosed manages the state transition to |closed|. Caller must
287 +// hold openerCounter lock.
288 +func transitionToClosed(r *FSRepo) error {
289 + r.state = closed
290 + if err := openerCounter.RemoveOpener(r.path); err != nil {
291 + return err
292 + }
293 + if countAfter := openerCounter.NumOpeners(r.path); countAfter == 0 {
294 + closer, ok := lockfiles[r.path]
295 + if !ok {
296 + return errors.New("package error: lockfile is not held")
297 + }
298 + if err := closer.Close(); err != nil {
299 + return err
300 + }
301 + }
302 + return nil
303 +}
repo/fsrepo/lock/lock.go renamed
+2 -2
@@ -1,4 +1,4 @@
1 -package daemon
1 +package lock
2
3 import (
4 "io"
@@ -10,6 +10,7 @@ import (
10 )
11
12 // LockFile is the filename of the daemon lock, relative to config dir
13 +// TODO rename repo lock and hide name
14 const LockFile = "daemon.lock"
15
16 func Lock(confdir string) (io.Closer, error) {
@@ -23,7 +24,6 @@ func Locked(confdir string) bool {
24 }
25 if lk, err := Lock(confdir); err != nil {
26 return true
26 -
27 } else {
28 lk.Close()
29 return false
repo/fsrepo/opener/counter.go
+4 -2
@@ -38,15 +38,17 @@ func (l *Counter) NumOpeners(repoPath string) int {
38
39 // AddOpener messages that an FSRepo holds a handle to the repo at this path.
40 // This method is not thread-safe. The caller must have this object locked.
41 -func (l *Counter) AddOpener(repoPath string) {
41 +func (l *Counter) AddOpener(repoPath string) error {
42 l.repos[key(repoPath)]++
43 + return nil
44 }
45
46 // RemoveOpener messgaes that an FSRepo no longer holds a handle to the repo at
47 // this path. This method is not thread-safe. The caller must have this object
48 // locked.
48 -func (l *Counter) RemoveOpener(repoPath string) {
49 +func (l *Counter) RemoveOpener(repoPath string) error {
50 l.repos[key(repoPath)]--
51 + return nil
52 }
53
54 func key(repoPath string) string {