~ubuntu-branches/ubuntu/saucy/juju-core/saucy-proposed

« back to all changes in this revision

Viewing changes to src/launchpad.net/juju-core/utils/fslock/fslock.go

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2013-07-11 17:18:27 UTC
  • mfrom: (1.1.1)
  • Revision ID: package-import@ubuntu.com-20130711171827-vjqkg40r0dlf7ys2
Tags: 1.11.2-0ubuntu1
* New upstream release.
* Make juju-core the default juju (LP: #1190634):
  - d/control: Add virtual package juju -> juju-core.
  - d/juju-core.postinst.in: Bump priority of alternatives over that of
    python juju packages.
* Enable for all architectures (LP: #1172505):
  - d/control: Version BD on golang-go to >= 2:1.1.1 to ensure CGO
    support for non-x86 archs, make juju-core Arch: any.
  - d/README.source: Dropped - no longer required.
* d/watch: Updated for new upstream tarball naming.

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2013 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
// On-disk mutex protecting a resource
 
5
//
 
6
// A lock is represented on disk by a directory of a particular name,
 
7
// containing an information file.  Taking a lock is done by renaming a
 
8
// temporary directory into place.  We use temporary directories because for
 
9
// all filesystems we believe that exactly one attempt to claim the lock will
 
10
// succeed and the others will fail.
 
11
package fslock
 
12
 
 
13
import (
 
14
        "bytes"
 
15
        "errors"
 
16
        "fmt"
 
17
        "io/ioutil"
 
18
        "os"
 
19
        "path"
 
20
        "regexp"
 
21
        "time"
 
22
 
 
23
        "launchpad.net/juju-core/log"
 
24
        "launchpad.net/juju-core/utils"
 
25
)
 
26
 
 
27
const (
 
28
        // NameRegexp specifies the regular expression used to itentify valid lock names.
 
29
        NameRegexp      = "^[a-z]+[a-z0-9.-]*$"
 
30
        heldFilename    = "held"
 
31
        messageFilename = "message"
 
32
)
 
33
 
 
34
var (
 
35
        ErrLockNotHeld = errors.New("lock not held")
 
36
        ErrTimeout     = errors.New("lock timeout exceeded")
 
37
 
 
38
        validName = regexp.MustCompile(NameRegexp)
 
39
 
 
40
        lockWaitDelay = 1 * time.Second
 
41
)
 
42
 
 
43
type Lock struct {
 
44
        name   string
 
45
        parent string
 
46
        nonce  []byte
 
47
}
 
48
 
 
49
// NewLock returns a new lock with the given name within the given lock
 
50
// directory, without acquiring it. The lock name must match the regular
 
51
// expression defined by NameRegexp.
 
52
func NewLock(lockDir, name string) (*Lock, error) {
 
53
        if !validName.MatchString(name) {
 
54
                return nil, fmt.Errorf("Invalid lock name %q.  Names must match %q", name, NameRegexp)
 
55
        }
 
56
        nonce, err := utils.NewUUID()
 
57
        if err != nil {
 
58
                return nil, err
 
59
        }
 
60
        lock := &Lock{
 
61
                name:   name,
 
62
                parent: lockDir,
 
63
                nonce:  nonce[:],
 
64
        }
 
65
        // Ensure the parent exists.
 
66
        if err := os.MkdirAll(lock.parent, 0755); err != nil {
 
67
                return nil, err
 
68
        }
 
69
        return lock, nil
 
70
}
 
71
 
 
72
func (lock *Lock) lockDir() string {
 
73
        return path.Join(lock.parent, lock.name)
 
74
}
 
75
 
 
76
func (lock *Lock) heldFile() string {
 
77
        return path.Join(lock.lockDir(), "held")
 
78
}
 
79
 
 
80
func (lock *Lock) messageFile() string {
 
81
        return path.Join(lock.lockDir(), "message")
 
82
}
 
83
 
 
84
// If message is set, it will write the message to the lock directory as the
 
85
// lock is taken.
 
86
func (lock *Lock) acquire(message string) (bool, error) {
 
87
        // If the lockDir exists, then the lock is held by someone else.
 
88
        _, err := os.Stat(lock.lockDir())
 
89
        if err == nil {
 
90
                return false, nil
 
91
        }
 
92
        if !os.IsNotExist(err) {
 
93
                return false, err
 
94
        }
 
95
        // Create a temporary directory (in the parent dir), and then move it to
 
96
        // the right name.  Using the same directory to make sure the directories
 
97
        // are on the same filesystem.  Use a directory name starting with "." as
 
98
        // it isn't a valid lock name.
 
99
        tempLockName := fmt.Sprintf(".%x", lock.nonce)
 
100
        tempDirName, err := ioutil.TempDir(lock.parent, tempLockName)
 
101
        if err != nil {
 
102
                return false, err // this shouldn't really fail...
 
103
        }
 
104
        // write nonce into the temp dir
 
105
        err = ioutil.WriteFile(path.Join(tempDirName, heldFilename), lock.nonce, 0755)
 
106
        if err != nil {
 
107
                return false, err
 
108
        }
 
109
        if message != "" {
 
110
                err = ioutil.WriteFile(path.Join(tempDirName, messageFilename), []byte(message), 0755)
 
111
                if err != nil {
 
112
                        return false, err
 
113
                }
 
114
        }
 
115
        // Now move the temp directory to the lock directory.
 
116
        err = os.Rename(tempDirName, lock.lockDir())
 
117
        if err != nil {
 
118
                // Any error on rename means we failed.
 
119
                // Beaten to it, clean up temporary directory.
 
120
                os.RemoveAll(tempDirName)
 
121
                return false, nil
 
122
        }
 
123
        // We now have the lock.
 
124
        return true, nil
 
125
}
 
126
 
 
127
// lockLoop tries to acquire the lock. If the acquisition fails, the
 
128
// continueFunc is run to see if the function should continue waiting.
 
129
func (lock *Lock) lockLoop(message string, continueFunc func() error) error {
 
130
        var heldMessage = ""
 
131
        for {
 
132
                acquired, err := lock.acquire(message)
 
133
                if err != nil {
 
134
                        return err
 
135
                }
 
136
                if acquired {
 
137
                        return nil
 
138
                }
 
139
                if err = continueFunc(); err != nil {
 
140
                        return err
 
141
                }
 
142
                currMessage := lock.Message()
 
143
                if currMessage != heldMessage {
 
144
                        log.Infof("attempted lock failed %q, %s, currently held: %s", lock.name, message, currMessage)
 
145
                        heldMessage = currMessage
 
146
                }
 
147
                time.Sleep(lockWaitDelay)
 
148
        }
 
149
        panic("unreachable")
 
150
}
 
151
 
 
152
// Lock blocks until it is able to acquire the lock.  Since we are dealing
 
153
// with sharing and locking using the filesystem, it is good behaviour to
 
154
// provide a message that is saved with the lock.  This is output in debugging
 
155
// information, and can be queried by any other Lock dealing with the same
 
156
// lock name and lock directory.
 
157
func (lock *Lock) Lock(message string) error {
 
158
        // The continueFunc is effectively a no-op, causing continual looping
 
159
        // until the lock is acquired.
 
160
        continueFunc := func() error { return nil }
 
161
        return lock.lockLoop(message, continueFunc)
 
162
}
 
163
 
 
164
// LockWithTimeout tries to acquire the lock. If it cannot acquire the lock
 
165
// within the given duration, it returns ErrTimeout.  See `Lock` for
 
166
// information about the message.
 
167
func (lock *Lock) LockWithTimeout(duration time.Duration, message string) error {
 
168
        deadline := time.Now().Add(duration)
 
169
        continueFunc := func() error {
 
170
                if time.Now().After(deadline) {
 
171
                        return ErrTimeout
 
172
                }
 
173
                return nil
 
174
        }
 
175
        return lock.lockLoop(message, continueFunc)
 
176
}
 
177
 
 
178
// Lock blocks until it is able to acquire the lock.  If the lock is failed to
 
179
// be acquired, the continueFunc is called prior to the sleeping.  If the
 
180
// continueFunc returns an error, that error is returned from LockWithFunc.
 
181
func (lock *Lock) LockWithFunc(message string, continueFunc func() error) error {
 
182
        return lock.lockLoop(message, continueFunc)
 
183
}
 
184
 
 
185
// IsHeld returns whether the lock is currently held by the receiver.
 
186
func (lock *Lock) IsLockHeld() bool {
 
187
        heldNonce, err := ioutil.ReadFile(lock.heldFile())
 
188
        if err != nil {
 
189
                return false
 
190
        }
 
191
        return bytes.Equal(heldNonce, lock.nonce)
 
192
}
 
193
 
 
194
// Unlock releases a held lock.  If the lock is not held ErrLockNotHeld is
 
195
// returned.
 
196
func (lock *Lock) Unlock() error {
 
197
        if !lock.IsLockHeld() {
 
198
                return ErrLockNotHeld
 
199
        }
 
200
        // To ensure reasonable unlocking, we should rename to a temp name, and delete that.
 
201
        tempLockName := fmt.Sprintf(".%s.%x", lock.name, lock.nonce)
 
202
        tempDirName := path.Join(lock.parent, tempLockName)
 
203
        // Now move the lock directory to the temp directory to release the lock.
 
204
        if err := os.Rename(lock.lockDir(), tempDirName); err != nil {
 
205
                return err
 
206
        }
 
207
        // And now cleanup.
 
208
        return os.RemoveAll(tempDirName)
 
209
}
 
210
 
 
211
// IsLocked returns true if the lock is currently held by anyone.
 
212
func (lock *Lock) IsLocked() bool {
 
213
        _, err := os.Stat(lock.heldFile())
 
214
        return err == nil
 
215
}
 
216
 
 
217
// BreakLock forcably breaks the lock that is currently being held.
 
218
func (lock *Lock) BreakLock() error {
 
219
        return os.RemoveAll(lock.lockDir())
 
220
}
 
221
 
 
222
// Message returns the saved message, or the empty string if there is no
 
223
// saved message.
 
224
func (lock *Lock) Message() string {
 
225
        message, err := ioutil.ReadFile(lock.messageFile())
 
226
        if err != nil {
 
227
                return ""
 
228
        }
 
229
        return string(message)
 
230
}