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 {