1//go:build windows
2
3package lock
4
5import (
6 "context"
7 "errors"
8 "fmt"
9 "math"
10 "os"
11 "time"
12
13 "golang.org/x/sys/windows"
14)
15
16// retrySleep is the interval between non-blocking lock retries in the
17// blocking File path.
18const retrySleep = 100 * time.Millisecond
19
20func lockFile(ctx context.Context, f *os.File) (func(), error) {
21 h := windows.Handle(f.Fd())
22 for {
23 ol := new(windows.Overlapped)
24 flags := uint32(windows.LOCKFILE_EXCLUSIVE_LOCK | windows.LOCKFILE_FAIL_IMMEDIATELY)
25 err := windows.LockFileEx(h, flags, 0, math.MaxUint32, math.MaxUint32, ol)
26 if err == nil {
27 return func() {
28 ol := new(windows.Overlapped)
29 _ = windows.UnlockFileEx(windows.Handle(f.Fd()), 0, math.MaxUint32, math.MaxUint32, ol)
30 }, nil
31 }
32 if !errors.Is(err, windows.ERROR_LOCK_VIOLATION) && !errors.Is(err, windows.ERROR_IO_PENDING) {
33 return nil, fmt.Errorf("LockFileEx: %w", err)
34 }
35 select {
36 case <-ctx.Done():
37 return nil, fmt.Errorf("acquire lock: %w", ctx.Err())
38 case <-time.After(retrySleep):
39 }
40 }
41}
42
43func tryLockFile(f *os.File) (func(), error) {
44 h := windows.Handle(f.Fd())
45 ol := new(windows.Overlapped)
46 flags := uint32(windows.LOCKFILE_EXCLUSIVE_LOCK | windows.LOCKFILE_FAIL_IMMEDIATELY)
47 if err := windows.LockFileEx(h, flags, 0, math.MaxUint32, math.MaxUint32, ol); err != nil {
48 if errors.Is(err, windows.ERROR_LOCK_VIOLATION) || errors.Is(err, windows.ERROR_IO_PENDING) {
49 return nil, ErrContended
50 }
51 return nil, fmt.Errorf("LockFileEx: %w", err)
52 }
53 return func() {
54 ol := new(windows.Overlapped)
55 _ = windows.UnlockFileEx(windows.Handle(f.Fd()), 0, math.MaxUint32, math.MaxUint32, ol)
56 }, nil
57}