-
Notifications
You must be signed in to change notification settings - Fork 2
/
file.go
156 lines (129 loc) · 4.75 KB
/
file.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
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
package file
import (
"context"
"os"
"time"
)
// Opens the file with shared lock. This function is the same as Open(path, os.O_RDONLY, RLock)
func OpenToRead(path string) (*os.File, error) {
return Open(path, os.O_RDONLY, RLock)
}
// Tries to lock and opens the file with shared lock. This function is the same as Open(path, os.O_RDONLY, TryRLock)
func TryOpenToRead(path string) (*os.File, error) {
return Open(path, os.O_RDONLY, TryRLock)
}
// Opens the file with exclusive lock. This function is the same as Open(path, os.O_RDWR, Lock)
func OpenToUpdate(path string) (*os.File, error) {
return Open(path, os.O_RDWR, Lock)
}
// Tries to lock and opens the file with exclusive lock. This function is the same as Open(path, os.O_RDWR, TryLock)
func TryOpenToUpdate(path string) (*os.File, error) {
return Open(path, os.O_RDWR, TryLock)
}
// Opens the file with exclusive locking. This function is the same as Open(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, TryLock)
func Create(path string) (*os.File, error) {
return Open(path, os.O_CREATE|os.O_EXCL|os.O_RDWR, TryLock)
}
// Opens the file with shared lock. If the file is already locked, tries to lock repeatedly until the conditions is met. This function is the same as OpenContext(ctx, retryDelay, path, RLockContext)
func OpenToReadContext(ctx context.Context, retryDelay time.Duration, path string) (*os.File, error) {
return OpenContext(ctx, retryDelay, path, os.O_RDONLY, RLockContext)
}
// Opens the file with exclusive lock. If the file is already locked, tries to lock repeatedly until the conditions is met. This function is the same as OpenContext(ctx, retryDelay, path, LockContext)
func OpenToUpdateContext(ctx context.Context, retryDelay time.Duration, path string) (*os.File, error) {
return OpenContext(ctx, retryDelay, path, os.O_RDWR, LockContext)
}
// Opens the file with passed locking function.
func Open(path string, flag int, fn func(*os.File) error) (*os.File, error) {
fp, err := openFile(path, flag)
if err != nil {
return nil, err
}
err = lock(fp, fn)
if err != nil {
_ = fp.Close()
}
return fp, err
}
// Opens the file with passed locking function. If failed, try to lock repeatedly until the conditions is met.
func OpenContext(ctx context.Context, retryDelay time.Duration, path string, flag int, fn func(context.Context, time.Duration, *os.File) error) (*os.File, error) {
fp, err := openFile(path, flag)
if err != nil {
return nil, err
}
err = fn(ctx, retryDelay, fp)
if err != nil {
_ = fp.Close()
}
return fp, err
}
func openFile(path string, flag int) (*os.File, error) {
var perm os.FileMode = 0600
if flag == os.O_RDONLY {
perm = 0400
}
fp, err := os.OpenFile(path, flag, perm)
if err != nil {
return nil, NewIOError(err.Error())
}
return fp, nil
}
// Places the exclusive lock on the file. If the file is already locked, waits until the file is released.
func Lock(fp *os.File) error {
return lock(fp, LockEX)
}
// Places the shared lock on the file. If the file is already locked, waits until the file is released.
func RLock(fp *os.File) error {
return lock(fp, LockSH)
}
// Places the exclusive lock on the file. If the file is already locked, returns an error immediately.
func TryLock(fp *os.File) error {
return lock(fp, TryLockEX)
}
// Places the shared lock on the file. If the file is already locked, returns an error immediately.
func TryRLock(fp *os.File) error {
return lock(fp, TryLockSH)
}
func lock(fp *os.File, fn func(*os.File) error) error {
if err := fn(fp); err != nil {
return NewLockError(err.Error())
}
return nil
}
// Places the exclusive lock on the file. If the file is already locked, tries to lock repeatedly until the conditions is met.
func LockContext(ctx context.Context, retryDelay time.Duration, fp *os.File) error {
return lockContext(ctx, retryDelay, fp, TryLock)
}
// Places the shared lock on the file. If the file is already locked, tries to lock repeatedly until the conditions is met.
func RLockContext(ctx context.Context, retryDelay time.Duration, fp *os.File) error {
return lockContext(ctx, retryDelay, fp, TryRLock)
}
func lockContext(ctx context.Context, retryDelay time.Duration, fp *os.File, fn func(*os.File) error) error {
if ctx.Err() != nil {
if ctx.Err() == context.Canceled {
return NewContextCanceled(ctx.Err().Error())
}
return NewContextDone(ctx.Err().Error())
}
for {
if err := fn(fp); err == nil {
return nil
}
select {
case <-ctx.Done():
if ctx.Err() == context.Canceled {
return NewContextCanceled(ctx.Err().Error())
}
return NewTimeoutError(fp.Name())
case <-time.After(retryDelay):
// try again
}
}
}
// Unlocks and closes the file
func Close(fp *os.File) (err error) {
defer func() { _ = fp.Close() }()
if err = Unlock(fp); err != nil {
return NewLockError(err.Error())
}
return nil
}