datadirlock_windows.go

 1//go:build windows
 2
 3package db
 4
 5import (
 6	"errors"
 7	"fmt"
 8	"math"
 9	"os"
10
11	"golang.org/x/sys/windows"
12)
13
14// errLockContended is returned by tryFileLock when the lock is held
15// by another process.
16var errLockContended = errors.New("file lock is held by another process")
17
18// tryFileLock takes an exclusive non-blocking lock on path via
19// LockFileEx. On success it returns a release function that unlocks
20// and closes the descriptor.
21//
22// The flags combine LOCKFILE_EXCLUSIVE_LOCK with LOCKFILE_FAIL_IMMEDIATELY
23// to mirror the BSD LOCK_EX|LOCK_NB semantics used on POSIX. The lock
24// is released when the file handle closes, including on process exit,
25// which gives us automatic stale-lock recovery without any bookkeeping.
26func tryFileLock(path string) (func(), error) {
27	f, err := os.OpenFile(path, os.O_RDWR|os.O_CREATE, 0o600)
28	if err != nil {
29		return nil, fmt.Errorf("open lock file: %w", err)
30	}
31	h := windows.Handle(f.Fd())
32	ol := new(windows.Overlapped)
33	flags := uint32(windows.LOCKFILE_EXCLUSIVE_LOCK | windows.LOCKFILE_FAIL_IMMEDIATELY)
34	if err := windows.LockFileEx(h, flags, 0, math.MaxUint32, math.MaxUint32, ol); err != nil {
35		_ = f.Close()
36		if errors.Is(err, windows.ERROR_LOCK_VIOLATION) || errors.Is(err, windows.ERROR_IO_PENDING) {
37			return nil, errLockContended
38		}
39		return nil, fmt.Errorf("LockFileEx: %w", err)
40	}
41	return func() {
42		ol := new(windows.Overlapped)
43		_ = windows.UnlockFileEx(windows.Handle(f.Fd()), 0, math.MaxUint32, math.MaxUint32, ol)
44		_ = f.Close()
45	}, nil
46}