|
|
@@ -2,17 +2,16 @@
|
|
|
|
|
|
package core
|
|
|
|
|
|
-// Windows 跨进程文件锁: 用 kernel32 的 LockFileEx / UnlockFileEx 对文件的一段字节
|
|
|
-// 区域加排他锁。纯标准库 (syscall 直接调 DLL), 不引入 golang.org/x/sys。
|
|
|
+// Windows 跨进程文件锁: 用 kernel32 的 LockFileEx / UnlockFileEx 对文件末尾之后
|
|
|
+// 的"虚拟字节区"加锁 (不覆盖任何有效页, 见 lockOverlapped 注释)。纯标准库。
|
|
|
|
|
|
import (
|
|
|
"syscall"
|
|
|
"unsafe"
|
|
|
)
|
|
|
|
|
|
-// 锁定区域: 文件偏移 0 起 lockByteCount 字节。加锁时文件至少已写入 4KB 头页
|
|
|
-// (新库在 WriteHeader 之后才 tryLock), 所以锁 [0, lockByteCount) 不会超出文件
|
|
|
-// 末尾。双方约定锁同一区域即可互斥 (与 Unix flock 的整文件锁语义等价)。
|
|
|
+// 锁定区域长度: 锁 [last_byte_after_end, +1) —— 跨进程互斥, 不碰头页/数据页/WAL 页。
|
|
|
+// 之前锁偏移=0 直接锁头页 (页 0), Windows NTFS 页粒度锁会让任何 Pread 页 0 返回零。
|
|
|
const lockByteCount = 1
|
|
|
|
|
|
const (
|
|
|
@@ -27,11 +26,17 @@ var (
|
|
|
procUnlockFileEx = kernel32.NewProc("UnlockFileEx")
|
|
|
)
|
|
|
|
|
|
-// lockOverlapped 构造指向文件偏移 0 的 OVERLAPPED (LockFileEx 用它指定锁起点)。
|
|
|
+// lockOverlapped 构造锁文件末尾之后的 OVERLAPPED (Offset=-1 = 0xFFFFFFFF):
|
|
|
+// 锁 [文件最后一字节之后, +1 字节) —— 既互斥又绝不覆盖任何有效数据页。
|
|
|
+//
|
|
|
+// 关键: Windows NTFS 的 LockFileEx 一旦落位, 会在页粒度 (4KB) 上锁住整个页;
|
|
|
+// 原设计锁偏移=0 直接锁页 0 (头页), 任何 Pread 页 0 都会读到零 —— 连锁持有方
|
|
|
+// 自己 ReadAt 也会中招, 引擎 ReadHeader 拿到 FirstWALPage=0, RecoverWAL 整个
|
|
|
+// 空转。改为"锁文件末尾之后"后完全避开所有有效页, 跨进程互斥语义不变。
|
|
|
func lockOverlapped() *syscall.Overlapped {
|
|
|
var ol syscall.Overlapped
|
|
|
- ol.Offset = 0
|
|
|
- ol.OffsetHigh = 0
|
|
|
+ ol.Offset = 0xFFFFFFFF
|
|
|
+ ol.OffsetHigh = 0xFFFFFFFF
|
|
|
return &ol
|
|
|
}
|
|
|
|
|
|
@@ -49,8 +54,7 @@ func (ff *File) winHandle() (syscall.Handle, error) {
|
|
|
return h, nil
|
|
|
}
|
|
|
|
|
|
-// tryLock 非阻塞地对文件区域加排他锁; 已被其它进程 (或本进程其它句柄) 锁定时
|
|
|
-// 返回 ErrDatabaseLocked。
|
|
|
+// tryLock 非阻塞地对文件末尾之后的虚拟字节加排他锁; 已被其它进程/句柄锁定时返回 ErrDatabaseLocked。
|
|
|
func (ff *File) tryLock() error {
|
|
|
if ff.fileLocked {
|
|
|
return nil
|
|
|
@@ -77,8 +81,8 @@ func (ff *File) tryLock() error {
|
|
|
return nil
|
|
|
}
|
|
|
|
|
|
-// tryRLock 非阻塞地对文件区域加【共享锁】(只读打开): 不加 EXCLUSIVE 标志。
|
|
|
-// 共享锁之间可共存, 与排他锁 (tryLock) 互斥; 冲突返回 ErrDatabaseLocked。
|
|
|
+// tryRLock 非阻塞地对文件加【共享锁】(只读打开): 不加 EXCLUSIVE 标志。
|
|
|
+// 共享锁之间可共存, 与排他锁互斥; 冲突返回 ErrDatabaseLocked。
|
|
|
func (ff *File) tryRLock() error {
|
|
|
if ff.fileLocked {
|
|
|
return nil
|