/
lock.go
77 lines (69 loc) · 1.71 KB
/
lock.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
// Copyright 2014 Canonical Ltd.
// Licensed under the AGPLv3, see LICENCE file for details.
package container
import (
"fmt"
"os"
"path/filepath"
"regexp"
)
const (
// NameRegexp specifies the regular expression used to identify valid lock names.
NameRegexp = "^[a-z]+[a-z0-9.-]*$"
)
var (
validName = regexp.MustCompile(NameRegexp)
)
type Lock struct {
name string
lockDir string
lockFile *os.File
}
// NewLock returns a new lock with the given name, using the given lock
// directory, without acquiring it. The lock name must match the regular
// expression defined by NameRegexp.
func NewLock(dir, name string) (*Lock, error) {
if !validName.MatchString(name) {
return nil, fmt.Errorf("Invalid lock name %q. Names must match %q", name, NameRegexp)
}
lockDir := filepath.Join(dir, name)
lock := &Lock{
name: name,
lockDir: lockDir,
}
// Ensure the lockDir exists.
if err := os.MkdirAll(lockDir, 0755); err != nil {
return nil, err
}
return lock, nil
}
// Lock blocks until it is able to acquire the lock. It is good behaviour to
// provide a message that is output in debugging information.
func (lock *Lock) Lock(message string) (err error) {
logger.Infof("acquire lock %q, %s", lock.name, message)
lock.lockFile, err = os.Open(lock.lockDir)
if err != nil {
return err
}
fd := int(lock.lockFile.Fd())
err = flockLock(fd)
if err != nil {
lock.lockFile.Close()
lock.lockFile = nil
}
return err
}
// Unlock releases a held lock.
func (lock *Lock) Unlock() error {
logger.Infof("release lock %q", lock.name)
if lock.lockFile == nil {
return nil
}
fd := int(lock.lockFile.Fd())
err := flockUnlock(fd)
if err == nil {
lock.lockFile.Close()
lock.lockFile = nil
}
return err
}