Skip to content
This repository was archived by the owner on Mar 9, 2019. It is now read-only.

Commit 1cb787e

Browse files
committed
windows: implement file locking
1 parent 033d4ec commit 1cb787e

File tree

2 files changed

+59
-9
lines changed

2 files changed

+59
-9
lines changed

bolt_windows.go

Lines changed: 59 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,38 @@ import (
88
"unsafe"
99
)
1010

11+
// LockFileEx code derived from golang build filemutex_windows.go @ v1.5.1
12+
var (
13+
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
14+
procLockFileEx = modkernel32.NewProc("LockFileEx")
15+
procUnlockFileEx = modkernel32.NewProc("UnlockFileEx")
16+
)
17+
18+
const (
19+
// see https://msdn.microsoft.com/en-us/library/windows/desktop/aa365203(v=vs.85).aspx
20+
flagLockExclusive = 2
21+
flagLockFailImmediately = 1
22+
23+
// see https://msdn.microsoft.com/en-us/library/windows/desktop/ms681382(v=vs.85).aspx
24+
errLockViolation syscall.Errno = 0x21
25+
)
26+
27+
func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
28+
r, _, err := procLockFileEx.Call(uintptr(h), uintptr(flags), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)))
29+
if r == 0 {
30+
return err
31+
}
32+
return nil
33+
}
34+
35+
func unlockFileEx(h syscall.Handle, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
36+
r, _, err := procUnlockFileEx.Call(uintptr(h), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)), 0)
37+
if r == 0 {
38+
return err
39+
}
40+
return nil
41+
}
42+
1143
var odirect int
1244

1345
// fdatasync flushes written data to a file descriptor.
@@ -16,13 +48,37 @@ func fdatasync(db *DB) error {
1648
}
1749

1850
// flock acquires an advisory lock on a file descriptor.
19-
func flock(f *os.File, _ bool, _ time.Duration) error {
20-
return nil
51+
func flock(f *os.File, exclusive bool, timeout time.Duration) error {
52+
var t time.Time
53+
for {
54+
// If we're beyond our timeout then return an error.
55+
// This can only occur after we've attempted a flock once.
56+
if t.IsZero() {
57+
t = time.Now()
58+
} else if timeout > 0 && time.Since(t) > timeout {
59+
return ErrTimeout
60+
}
61+
62+
var flag uint32 = flagLockFailImmediately
63+
if exclusive {
64+
flag |= flagLockExclusive
65+
}
66+
67+
err := lockFileEx(syscall.Handle(f.Fd()), flag, 0, 1, 0, &syscall.Overlapped{})
68+
if err == nil {
69+
return nil
70+
} else if err != errLockViolation {
71+
return err
72+
}
73+
74+
// Wait for a bit and try again.
75+
time.Sleep(50 * time.Millisecond)
76+
}
2177
}
2278

2379
// funlock releases an advisory lock on a file descriptor.
2480
func funlock(f *os.File) error {
25-
return nil
81+
return unlockFileEx(syscall.Handle(f.Fd()), 0, 1, 0, &syscall.Overlapped{})
2682
}
2783

2884
// mmap memory maps a DB's data file.

db_test.go

Lines changed: 0 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -39,9 +39,6 @@ func TestOpen(t *testing.T) {
3939

4040
// Ensure that opening an already open database file will timeout.
4141
func TestOpen_Timeout(t *testing.T) {
42-
if runtime.GOOS == "windows" {
43-
t.Skip("timeout not supported on windows")
44-
}
4542
if runtime.GOOS == "solaris" {
4643
t.Skip("solaris fcntl locks don't support intra-process locking")
4744
}
@@ -66,9 +63,6 @@ func TestOpen_Timeout(t *testing.T) {
6663

6764
// Ensure that opening an already open database file will wait until its closed.
6865
func TestOpen_Wait(t *testing.T) {
69-
if runtime.GOOS == "windows" {
70-
t.Skip("timeout not supported on windows")
71-
}
7266
if runtime.GOOS == "solaris" {
7367
t.Skip("solaris fcntl locks don't support intra-process locking")
7468
}

0 commit comments

Comments
 (0)