| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148 |
- //go:build windows
- package core
- // Windows 跨进程文件锁: 用 kernel32 的 LockFileEx / UnlockFileEx 对文件末尾之后
- // 的"虚拟字节区"加锁 (不覆盖任何有效页, 见 tryLock 注释)。纯标准库。
- import (
- "syscall"
- "unsafe"
- )
- // 锁定区域长度: 锁 [fileSize, fileSize+1) —— 跨进程互斥, 不碰头页/数据页/WAL 页。
- // 历史坑: 原设计锁偏移=0 直接锁头页 (页 0), Windows NTFS 页粒度锁 (4KB) 会让
- // 任何 Pread 页 0 返回零数据, 连锁持有方自己 ReadAt 也中招 —— 引擎 ReadHeader
- // 拿到 FirstWALPage=0, RecoverWAL 整个空转。
- const lockByteCount = 1
- const (
- winLockFileExclusive = 0x00000002 // LOCKFILE_EXCLUSIVE_LOCK
- winLockFileFailImmediately = 0x00000001 // LOCKFILE_FAIL_IMMEDIATELY (非阻塞)
- winErrLockViolation = 33 // ERROR_LOCK_VIOLATION
- )
- var (
- kernel32 = syscall.NewLazyDLL("kernel32.dll")
- procLockFileEx = kernel32.NewProc("LockFileEx")
- procUnlockFileEx = kernel32.NewProc("UnlockFileEx")
- )
- // overlappedFromOffset 根据 64 位文件偏移构造 LockFileEx 用的 OVERLAPPED。
- func overlappedFromOffset(offset uint64) *syscall.Overlapped {
- var ol syscall.Overlapped
- ol.Offset = uint32(offset & 0xFFFFFFFF)
- ol.OffsetHigh = uint32(offset >> 32)
- return &ol
- }
- func (ff *File) winHandle() (syscall.Handle, error) {
- sc, err := ff.f.SyscallConn()
- if err != nil {
- return 0, err
- }
- var h syscall.Handle
- if err := sc.Control(func(fd uintptr) {
- h = syscall.Handle(fd)
- }); err != nil {
- return 0, err
- }
- return h, nil
- }
- // fileSize 返回文件当前大小 (用于计算锁偏移)。
- func (ff *File) fileSize() (int64, error) {
- st, err := ff.f.Stat()
- if err != nil {
- return 0, err
- }
- return st.Size(), nil
- }
- // tryLock 非阻塞地对文件末尾之后的虚拟字节加排他锁; 已被其它进程/句柄锁定时返回 ErrDatabaseLocked。
- func (ff *File) tryLock() error {
- if ff.fileLocked {
- return nil
- }
- size, err := ff.fileSize()
- if err != nil {
- return err
- }
- // 锁 [EOF, EOF+1) —— 所有有效页都在 <EOF, 所以锁区间永远不重叠
- lockStart := uint64(size)
- h, err := ff.winHandle()
- if err != nil {
- return err
- }
- ol := overlappedFromOffset(lockStart)
- r1, _, e1 := syscall.Syscall6(procLockFileEx.Addr(), 6,
- uintptr(h),
- uintptr(winLockFileExclusive|winLockFileFailImmediately),
- 0,
- uintptr(lockByteCount),
- 0,
- uintptr(unsafe.Pointer(ol)))
- if r1 == 0 {
- if e1 == syscall.Errno(winErrLockViolation) {
- return ErrDatabaseLocked
- }
- return e1
- }
- ff.fileLocked = true
- ff.lockOffset = lockStart // 保存偏移 —— unlock 必须用完全相同的区间
- return nil
- }
- // tryRLock 非阻塞地对文件加【共享锁】(只读打开): 不加 EXCLUSIVE 标志。
- // 共享锁之间可共存, 与排他锁互斥; 冲突返回 ErrDatabaseLocked。
- func (ff *File) tryRLock() error {
- if ff.fileLocked {
- return nil
- }
- size, err := ff.fileSize()
- if err != nil {
- return err
- }
- lockStart := uint64(size)
- h, err := ff.winHandle()
- if err != nil {
- return err
- }
- ol := overlappedFromOffset(lockStart)
- r1, _, e1 := syscall.Syscall6(procLockFileEx.Addr(), 6,
- uintptr(h),
- uintptr(winLockFileFailImmediately), // 共享锁 (无 LOCKFILE_EXCLUSIVE_LOCK)
- 0,
- uintptr(lockByteCount),
- 0,
- uintptr(unsafe.Pointer(ol)))
- if r1 == 0 {
- if e1 == syscall.Errno(winErrLockViolation) {
- return ErrDatabaseLocked
- }
- return e1
- }
- ff.fileLocked = true
- ff.lockOffset = lockStart
- return nil
- }
- // unlock 释放文件锁 (Close 路径调用; 错误忽略 —— 句柄关闭同样会释放)。
- // 必须用与 tryLock 时完全相同的偏移区间 (Windows LockFileEx 解锁不支持通配符)。
- func (ff *File) unlock() {
- if !ff.fileLocked {
- return
- }
- if h, err := ff.winHandle(); err == nil {
- ol := overlappedFromOffset(ff.lockOffset)
- _, _, _ = syscall.Syscall6(procUnlockFileEx.Addr(), 5,
- uintptr(h),
- 0,
- uintptr(lockByteCount),
- 0,
- uintptr(unsafe.Pointer(ol)),
- 0)
- }
- ff.fileLocked = false
- ff.lockOffset = 0
- }
|