Skip to content

Commit d9eb200

Browse files
committed
add a pager for windows
1 parent 478877b commit d9eb200

File tree

2 files changed

+173
-0
lines changed

2 files changed

+173
-0
lines changed

db/pager_unix.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
// +build !windows
2+
13
// unix implementation of the `pager` interface (the file reader) with POSIX
24
// advisory locking
35

db/pager_windows.go

Lines changed: 171 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,171 @@
1+
// windows implementation of the `pager` interface (the file reader) with LockFileEx
2+
// locking
3+
4+
// references:
5+
// winapi docs: https://docs.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-lockfileex
6+
// sqlite3 winlock: https://github.com/sqlite/sqlite/blob/c398c65bee850b6b8f24a44852872a27f114535d/src/os_win.c#L3236
7+
// golang windows syscalls from go/internal:
8+
// src/cmd/go/internal/lockedfile/internal/filelock/filelock_windows.go
9+
// src/internal/syscall/windows/syscall_windows.go
10+
11+
package db
12+
13+
import (
14+
"errors"
15+
"os"
16+
"syscall"
17+
"unsafe"
18+
19+
"golang.org/x/exp/mmap"
20+
)
21+
22+
const (
23+
seek_set = 0 // should be defined in syscall
24+
sqlite_pending_byte = 0x40000000
25+
sqlite_reserved_byte = sqlite_pending_byte + 1
26+
sqlite_shared_first = sqlite_pending_byte + 2
27+
sqlite_shared_size = 510
28+
)
29+
30+
var (
31+
modkernel32 = syscall.NewLazyDLL("kernel32.dll")
32+
procLockFileEx = modkernel32.NewProc("LockFileEx")
33+
procUnlockFileEx = modkernel32.NewProc("UnlockFileEx")
34+
)
35+
36+
const (
37+
reserved uint32 = 0
38+
lockfileFailImmediately uint32 = 1
39+
lockfileExclusiveLock uint32 = 2
40+
sqliteLockfileFlags uint32 = lockfileFailImmediately | lockfileExclusiveLock
41+
)
42+
43+
func lockFileEx(h syscall.Handle, flags, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
44+
r1, _, e1 := syscall.Syscall6(procLockFileEx.Addr(), 6, uintptr(h), uintptr(flags), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)))
45+
if r1 == 0 {
46+
if e1 != 0 {
47+
err = error(e1)
48+
} else {
49+
err = syscall.EINVAL
50+
}
51+
}
52+
return
53+
}
54+
55+
func unlockFileEx(h syscall.Handle, reserved, locklow, lockhigh uint32, ol *syscall.Overlapped) (err error) {
56+
r1, _, e1 := syscall.Syscall6(procUnlockFileEx.Addr(), 5, uintptr(h), uintptr(reserved), uintptr(locklow), uintptr(lockhigh), uintptr(unsafe.Pointer(ol)), 0)
57+
if r1 == 0 {
58+
if e1 != 0 {
59+
err = error(e1)
60+
} else {
61+
err = syscall.EINVAL
62+
}
63+
}
64+
return
65+
}
66+
67+
// LockState is used to represent the program's opinion of whether a file is locked or not
68+
// this does not represent the lock state of the file - we can't distinguish between
69+
// a file that is locked by another process and failing to acquire a lock
70+
type LockState int
71+
72+
const (
73+
Unlocked LockState = iota
74+
Locked
75+
)
76+
77+
type filePager struct {
78+
f *os.File
79+
mm *mmap.ReaderAt
80+
81+
//TODO: mutex
82+
state LockState
83+
}
84+
85+
func newFilePager(file string) (*filePager, error) {
86+
f, err := os.Open(file)
87+
if err != nil {
88+
return nil, err
89+
}
90+
mm, err := mmap.Open(file)
91+
if err != nil {
92+
f.Close()
93+
return nil, err
94+
}
95+
return &filePager{
96+
f: f,
97+
mm: mm,
98+
state: Unlocked,
99+
}, nil
100+
}
101+
102+
// pages start counting at 1
103+
func (f *filePager) page(id int, pagesize int) ([]byte, error) {
104+
buf := make([]byte, pagesize)
105+
_, err := f.mm.ReadAt(buf[:], int64(id-1)*int64(pagesize))
106+
return buf, err
107+
}
108+
109+
func (f *filePager) lock(start, len uint32) error {
110+
ol := new(syscall.Overlapped)
111+
ol.Offset = start
112+
return lockFileEx(syscall.Handle(f.f.Fd()), sqliteLockfileFlags, reserved, len, 0, ol)
113+
}
114+
115+
func (f *filePager) unlock(start, len uint32) error {
116+
ol := new(syscall.Overlapped)
117+
ol.Offset = start
118+
return unlockFileEx(syscall.Handle(f.f.Fd()), reserved, len, 0, ol)
119+
}
120+
121+
func (f *filePager) RLock() error {
122+
// Set a 'SHARED' lock, following winLock() logic from sqlite3.c
123+
if f.state == Locked {
124+
return errors.New("trying to lock a locked lock") // panic?
125+
}
126+
127+
// - get PENDING lock
128+
129+
// TODO: retry , see sqlite3.c
130+
if err := f.lock(sqlite_pending_byte, 1); err != nil {
131+
return err
132+
}
133+
134+
defer func() {
135+
// - drop the pending lock. No idea what to do with the error :/
136+
f.unlock(sqlite_pending_byte, 1)
137+
}()
138+
139+
// Get the read-lock
140+
if err := f.lock(sqlite_shared_first, sqlite_shared_size); err != nil {
141+
return err
142+
}
143+
f.state = Locked
144+
return nil
145+
}
146+
147+
func (f *filePager) RUnlock() error {
148+
if f.state == Unlocked {
149+
return errors.New("trying to unlock an unlocked lock") // panic?
150+
}
151+
if err := f.lock(sqlite_shared_first, sqlite_shared_size); err != nil {
152+
return err
153+
}
154+
f.state = Unlocked
155+
return nil
156+
}
157+
158+
// True if there is a 'reserved' lock on the database, by any process.
159+
func (f *filePager) CheckReservedLock() (bool, error) {
160+
// per SQLite's winCheckReservedLock()
161+
err := f.lock(sqlite_reserved_byte, 1)
162+
if err == nil {
163+
f.unlock(sqlite_reserved_byte, 1)
164+
}
165+
return err == nil, err
166+
}
167+
168+
func (f *filePager) Close() error {
169+
f.f.Close()
170+
return f.mm.Close()
171+
}

0 commit comments

Comments
 (0)