1//go:build !windows
2
3package lock
4
5import (
6 "context"
7 "errors"
8 "fmt"
9 "os"
10 "time"
11
12 "golang.org/x/sys/unix"
13)
14
15// retrySleep is the interval between non-blocking flock retries in the
16// blocking File path. Small enough that contention resolution feels
17// snappy; large enough that we don't burn a CPU spinning.
18const retrySleep = 100 * time.Millisecond
19
20func lockFile(ctx context.Context, f *os.File) (func(), error) {
21 for {
22 err := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB)
23 if err == nil {
24 return func() { _ = unix.Flock(int(f.Fd()), unix.LOCK_UN) }, nil
25 }
26 if !errors.Is(err, unix.EWOULDBLOCK) {
27 return nil, fmt.Errorf("flock: %w", err)
28 }
29 select {
30 case <-ctx.Done():
31 return nil, fmt.Errorf("acquire lock: %w", ctx.Err())
32 case <-time.After(retrySleep):
33 }
34 }
35}
36
37func tryLockFile(f *os.File) (func(), error) {
38 if err := unix.Flock(int(f.Fd()), unix.LOCK_EX|unix.LOCK_NB); err != nil {
39 if errors.Is(err, unix.EWOULDBLOCK) {
40 return nil, ErrContended
41 }
42 return nil, fmt.Errorf("flock: %w", err)
43 }
44 return func() { _ = unix.Flock(int(f.Fd()), unix.LOCK_UN) }, nil
45}