file_lock_windows.go 3.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124
  1. //go:build windows
  2. package core
  3. // Windows 跨进程文件锁: 用 kernel32 的 LockFileEx / UnlockFileEx 对文件的一段字节
  4. // 区域加排他锁。纯标准库 (syscall 直接调 DLL), 不引入 golang.org/x/sys。
  5. import (
  6. "syscall"
  7. "unsafe"
  8. )
  9. // 锁定区域: 文件偏移 0 起 lockByteCount 字节。加锁时文件至少已写入 4KB 头页
  10. // (新库在 WriteHeader 之后才 tryLock), 所以锁 [0, lockByteCount) 不会超出文件
  11. // 末尾。双方约定锁同一区域即可互斥 (与 Unix flock 的整文件锁语义等价)。
  12. const lockByteCount = 1
  13. const (
  14. winLockFileExclusive = 0x00000002 // LOCKFILE_EXCLUSIVE_LOCK
  15. winLockFileFailImmediately = 0x00000001 // LOCKFILE_FAIL_IMMEDIATELY (非阻塞)
  16. winErrLockViolation = 33 // ERROR_LOCK_VIOLATION
  17. )
  18. var (
  19. kernel32 = syscall.NewLazyDLL("kernel32.dll")
  20. procLockFileEx = kernel32.NewProc("LockFileEx")
  21. procUnlockFileEx = kernel32.NewProc("UnlockFileEx")
  22. )
  23. // lockOverlapped 构造指向文件偏移 0 的 OVERLAPPED (LockFileEx 用它指定锁起点)。
  24. func lockOverlapped() *syscall.Overlapped {
  25. var ol syscall.Overlapped
  26. ol.Offset = 0
  27. ol.OffsetHigh = 0
  28. return &ol
  29. }
  30. func (ff *File) winHandle() (syscall.Handle, error) {
  31. sc, err := ff.f.SyscallConn()
  32. if err != nil {
  33. return 0, err
  34. }
  35. var h syscall.Handle
  36. if err := sc.Control(func(fd uintptr) {
  37. h = syscall.Handle(fd)
  38. }); err != nil {
  39. return 0, err
  40. }
  41. return h, nil
  42. }
  43. // tryLock 非阻塞地对文件区域加排他锁; 已被其它进程 (或本进程其它句柄) 锁定时
  44. // 返回 ErrDatabaseLocked。
  45. func (ff *File) tryLock() error {
  46. if ff.fileLocked {
  47. return nil
  48. }
  49. h, err := ff.winHandle()
  50. if err != nil {
  51. return err
  52. }
  53. ol := lockOverlapped()
  54. r1, _, e1 := syscall.Syscall6(procLockFileEx.Addr(), 6,
  55. uintptr(h),
  56. uintptr(winLockFileExclusive|winLockFileFailImmediately),
  57. 0,
  58. uintptr(lockByteCount),
  59. 0,
  60. uintptr(unsafe.Pointer(ol)))
  61. if r1 == 0 {
  62. if e1 == syscall.Errno(winErrLockViolation) {
  63. return ErrDatabaseLocked
  64. }
  65. return e1
  66. }
  67. ff.fileLocked = true
  68. return nil
  69. }
  70. // tryRLock 非阻塞地对文件区域加【共享锁】(只读打开): 不加 EXCLUSIVE 标志。
  71. // 共享锁之间可共存, 与排他锁 (tryLock) 互斥; 冲突返回 ErrDatabaseLocked。
  72. func (ff *File) tryRLock() error {
  73. if ff.fileLocked {
  74. return nil
  75. }
  76. h, err := ff.winHandle()
  77. if err != nil {
  78. return err
  79. }
  80. ol := lockOverlapped()
  81. r1, _, e1 := syscall.Syscall6(procLockFileEx.Addr(), 6,
  82. uintptr(h),
  83. uintptr(winLockFileFailImmediately), // 共享锁 (无 LOCKFILE_EXCLUSIVE_LOCK)
  84. 0,
  85. uintptr(lockByteCount),
  86. 0,
  87. uintptr(unsafe.Pointer(ol)))
  88. if r1 == 0 {
  89. if e1 == syscall.Errno(winErrLockViolation) {
  90. return ErrDatabaseLocked
  91. }
  92. return e1
  93. }
  94. ff.fileLocked = true
  95. return nil
  96. }
  97. // unlock 释放文件锁 (Close 路径调用; 错误忽略 —— 句柄关闭同样会释放)。
  98. func (ff *File) unlock() {
  99. if !ff.fileLocked {
  100. return
  101. }
  102. if h, err := ff.winHandle(); err == nil {
  103. ol := lockOverlapped()
  104. _, _, _ = syscall.Syscall6(procUnlockFileEx.Addr(), 5,
  105. uintptr(h),
  106. 0,
  107. uintptr(lockByteCount),
  108. 0,
  109. uintptr(unsafe.Pointer(ol)),
  110. 0)
  111. }
  112. ff.fileLocked = false
  113. }