-
Notifications
You must be signed in to change notification settings - Fork 1
/
Copy pathmain.go
134 lines (121 loc) · 2.58 KB
/
main.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
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
// Package fcntllock provides simple whole file lock methods based on fcntl
//
// Lock functions create lock directory if absent
package fcntllock
import (
"context"
"errors"
"io"
"os"
"path/filepath"
"syscall"
"time"
"github.com/opensvc/locker"
)
type (
Locker = locker.Locker
ReadWriteSeekCloser interface {
io.ReadWriteSeeker
io.Closer
}
// Lock implement fcntl lock features
Lock struct {
path string
ReadWriteSeekCloser
fd uintptr
}
)
var (
lockDirPerm os.FileMode = 0700
)
// New create a new fcntl lock
func New(path string) Locker {
return &Lock{
path: path,
}
}
// TryLock acquires an exclusive write file lock (non blocking)
func (lck *Lock) TryLock() error {
if err := createLockDir(lck.path); err != nil {
return err
}
return lck.lock(false)
}
// UnLock release lock
func (lck Lock) UnLock() (err error) {
ft := &syscall.Flock_t{
Start: 0,
Len: 0,
Pid: 0,
Type: syscall.F_UNLCK,
Whence: io.SeekStart,
}
err = syscall.FcntlFlock(lck.fd, syscall.F_SETLK, ft)
return
}
// LockContext repeat TryLock with retry delay until succeed or context Done
func (lck *Lock) LockContext(ctx context.Context, retryDelay time.Duration) error {
if err := createLockDir(lck.path); err != nil {
return err
}
return lck.try(ctx, lck.TryLock, retryDelay)
}
func (lck *Lock) lock(blocking bool) (err error) {
if lck.ReadWriteSeekCloser == nil {
file, err := os.OpenFile(lck.path, os.O_CREATE|os.O_RDWR|os.O_SYNC, 0666)
if err != nil {
return err
}
lck.fd = file.Fd()
lck.ReadWriteSeekCloser = file
}
ft := &syscall.Flock_t{
Start: 0,
Len: 0,
Pid: int32(os.Getpid()),
Type: syscall.F_WRLCK,
Whence: io.SeekStart,
}
var cmd int
if blocking {
cmd = syscall.F_SETLKW
} else {
cmd = syscall.F_SETLK
}
if err = syscall.FcntlFlock(lck.fd, cmd, ft); err != nil {
_ = lck.Close()
lck.ReadWriteSeekCloser = nil
}
return
}
func (lck *Lock) try(ctx context.Context, fn func() error, retryDelay time.Duration) error {
for {
if err := fn(); err == nil {
return nil
} else if serr, ok := err.(syscall.Errno); !ok || (serr != syscall.EAGAIN) {
// return immediately
return err
}
select {
case <-ctx.Done():
// context reach end
return ctx.Err()
case <-time.After(retryDelay):
// will try again fn()
}
}
}
func createLockDir(path string) (err error) {
dir := filepath.Dir(path)
info, err := os.Stat(dir)
if err == nil {
if info.IsDir() {
return
}
return errors.New("already exists and is not directory: " + dir)
}
if os.IsNotExist(err) {
return os.MkdirAll(dir, lockDirPerm)
}
return err
}