12
12
// To avoid unlocking files prematurely when the same file is opened through different descriptors,
13
13
// we allow only one read-lock at a time.
14
14
//
15
- // This code is adapted from the Go package (go.12 ):
16
- // https://github.com/golang/go/blob/release-branch.go1.12 /src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go
15
+ // This code is adapted from the Go package (go.22 ):
16
+ // https://github.com/golang/go/blob/release-branch.go1.22 /src/cmd/go/internal/lockedfile/internal/filelock/filelock_fcntl.go
17
17
18
18
//go:build aix || (solaris && !illumos)
19
19
@@ -23,8 +23,10 @@ import (
23
23
"errors"
24
24
"io"
25
25
"io/fs"
26
+ "math/rand"
26
27
"sync"
27
28
"syscall"
29
+ "time"
28
30
29
31
"golang.org/x/sys/unix"
30
32
)
@@ -183,15 +185,81 @@ func (f *Flock) doLock(cmd cmdType, lt lockType, blocking bool) (bool, error) {
183
185
wait <- f
184
186
}
185
187
186
- err = setlkw (f .fh .Fd (), cmd , lt )
188
+ // Spurious EDEADLK errors arise on platforms that compute deadlock graphs at
189
+ // the process, rather than thread, level. Consider processes P and Q, with
190
+ // threads P.1, P.2, and Q.3. The following trace is NOT a deadlock, but will be
191
+ // reported as a deadlock on systems that consider only process granularity:
192
+ //
193
+ // P.1 locks file A.
194
+ // Q.3 locks file B.
195
+ // Q.3 blocks on file A.
196
+ // P.2 blocks on file B. (This is erroneously reported as a deadlock.)
197
+ // P.1 unlocks file A.
198
+ // Q.3 unblocks and locks file A.
199
+ // Q.3 unlocks files A and B.
200
+ // P.2 unblocks and locks file B.
201
+ // P.2 unlocks file B.
202
+ //
203
+ // These spurious errors were observed in practice on AIX and Solaris in
204
+ // cmd/go: see https://golang.org/issue/32817.
205
+ //
206
+ // We work around this bug by treating EDEADLK as always spurious. If there
207
+ // really is a lock-ordering bug between the interacting processes, it will
208
+ // become a livelock instead, but that's not appreciably worse than if we had
209
+ // a proper flock implementation (which generally does not even attempt to
210
+ // diagnose deadlocks).
211
+ //
212
+ // In the above example, that changes the trace to:
213
+ //
214
+ // P.1 locks file A.
215
+ // Q.3 locks file B.
216
+ // Q.3 blocks on file A.
217
+ // P.2 spuriously fails to lock file B and goes to sleep.
218
+ // P.1 unlocks file A.
219
+ // Q.3 unblocks and locks file A.
220
+ // Q.3 unlocks files A and B.
221
+ // P.2 wakes up and locks file B.
222
+ // P.2 unlocks file B.
223
+ //
224
+ // We know that the retry loop will not introduce a *spurious* livelock
225
+ // because, according to the POSIX specification, EDEADLK is only to be
226
+ // returned when “the lock is blocked by a lock from another process”.
227
+ // If that process is blocked on some lock that we are holding, then the
228
+ // resulting livelock is due to a real deadlock (and would manifest as such
229
+ // when using, for example, the flock implementation of this package).
230
+ // If the other process is *not* blocked on some other lock that we are
231
+ // holding, then it will eventually release the requested lock.
232
+
233
+ nextSleep := 1 * time .Millisecond
234
+ const maxSleep = 500 * time .Millisecond
235
+ for {
236
+ err = setlkw (f .fh .Fd (), cmd , lt )
237
+ if ! errors .Is (err , syscall .EDEADLK ) {
238
+ break
239
+ }
240
+
241
+ time .Sleep (nextSleep )
242
+
243
+ nextSleep += nextSleep
244
+ if nextSleep > maxSleep {
245
+ nextSleep = maxSleep
246
+ }
247
+ // Apply 10% jitter to avoid synchronizing collisions when we finally unblock.
248
+ nextSleep += time .Duration ((0.1 * rand .Float64 () - 0.05 ) * float64 (nextSleep ))
249
+ }
250
+
187
251
if err != nil {
188
252
f .doUnlock ()
189
253
190
254
if cmd == tryLock && errors .Is (err , unix .EACCES ) {
191
255
return false , nil
192
256
}
193
257
194
- return false , err
258
+ return false , & fs.PathError {
259
+ Op : lt .String (),
260
+ Path : f .Path (),
261
+ Err : err ,
262
+ }
195
263
}
196
264
197
265
return true , nil
0 commit comments