-
Notifications
You must be signed in to change notification settings - Fork 113
/
filelocks.go
161 lines (137 loc) · 4.23 KB
/
filelocks.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
157
158
159
160
161
// Copyright 2018-2021 CERN
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
// In applying this license, CERN does not waive the privileges and immunities
// granted to it by virtue of its status as an Intergovernmental Organization
// or submit itself to any jurisdiction.
package filelocks
import (
"errors"
"io/fs"
"os"
"sync"
"time"
"github.com/gofrs/flock"
)
var _localLocks sync.Map
// getMutexedFlock returns a new Flock struct for the given file.
// If there is already one in the local store, it returns nil.
// The caller has to wait until it can get a new one out of this
// mehtod.
func getMutexedFlock(file string) *flock.Flock {
// Is there lock already?
if _, ok := _localLocks.Load(file); ok {
// There is already a lock for this file, another can not be acquired
return nil
}
// Acquire the write log on the target node first.
l := flock.New(file)
_localLocks.Store(file, l)
return l
}
// releaseMutexedFlock releases a Flock object that was acquired
// before by the getMutexedFlock function.
func releaseMutexedFlock(file string) {
if len(file) > 0 {
_localLocks.Delete(file)
}
}
// acquireWriteLog acquires a lock on a file or directory.
// if the parameter write is true, it gets an exclusive write lock, otherwise a shared read lock.
// The function returns a Flock object, unlocking has to be done in the calling function.
func acquireLock(file string, write bool) (*flock.Flock, error) {
var err error
// Create a file to carry the log
n := flockFile(file)
if len(n) == 0 {
return nil, errors.New("lock path is empty")
}
var flock *flock.Flock
for i := 1; i <= 10; i++ {
if flock = getMutexedFlock(n); flock != nil {
break
}
w := time.Duration(i*3) * time.Millisecond
time.Sleep(w)
}
if flock == nil {
return nil, errors.New("unable to acquire a lock on the file")
}
var ok bool
for i := 1; i <= 10; i++ {
if write {
ok, err = flock.TryLock()
} else {
ok, err = flock.TryRLock()
}
if ok {
break
}
time.Sleep(time.Duration(i*3) * time.Millisecond)
}
if !ok {
err = errors.New("could not acquire lock after wait")
}
if err != nil {
return nil, err
}
return flock, nil
}
// flockFile returns the flock filename for a given file name
// it returns an empty string if the input is empty
func flockFile(file string) string {
var n string
if len(file) > 0 {
n = file + ".flock"
}
return n
}
// AcquireReadLock tries to acquire a shared lock to read from the
// file and returns a lock object or an error accordingly.
// Call with the file to lock. This function creates .lock file next
// to it.
func AcquireReadLock(file string) (*flock.Flock, error) {
return acquireLock(file, false)
}
// AcquireWriteLock tries to acquire a shared lock to write from the
// file and returns a lock object or an error accordingly.
// Call with the file to lock. This function creates an extra .lock
// file next to it.
func AcquireWriteLock(file string) (*flock.Flock, error) {
return acquireLock(file, true)
}
// ReleaseLock releases a lock from a file that was previously created
// by AcquireReadLock or AcquireWriteLock.
func ReleaseLock(lock *flock.Flock) error {
// there is a probability that if the file can not be unlocked,
// we also can not remove the file. We will only try to remove if it
// was successfully unlocked.
var err error
n := lock.Path()
// There is already a lock for this file
err = lock.Unlock()
if err == nil {
if !lock.Locked() && !lock.RLocked() {
err = os.Remove(n)
// there is a concurrency issue when deleting the file
// see https://github.com/owncloud/ocis/issues/3757
// for now we just ignore "not found" errors when they pop up
if err != nil && errors.Is(err, fs.ErrNotExist) {
err = nil
}
}
}
releaseMutexedFlock(n)
return err
}