lock_windows.go

 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}