main.go 2.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. package main
  2. // =============================================================================
  3. // file_lock_verify — 跨进程文件锁验证
  4. //
  5. // 父进程:
  6. // 1. 先建一个数据库;
  7. // 2. 起子进程打开该库并持有锁 3 秒 (模拟另一个进程正在使用);
  8. // 3. 子进程持锁期间, 父进程尝试 Open 同一文件 —— 必须被拒绝, 且错误可用
  9. // errors.Is(err, core.ErrDatabaseLocked) 判定;
  10. // 4. 等子进程退出 (Close 释放锁);
  11. // 5. 父进程再次 Open —— 必须成功。
  12. //
  13. // 这是文件锁的端到端验证: 锁确实由 OS 内核跨进程互斥, 且释放后立即可用。
  14. // =============================================================================
  15. import (
  16. "errors"
  17. "fmt"
  18. "os"
  19. "os/exec"
  20. "path/filepath"
  21. "time"
  22. "git.ycl.pub/yl32940/TsDB2/DB/core"
  23. )
  24. const (
  25. envChild = "FILE_LOCK_CHILD"
  26. envDB = "FILE_LOCK_DB"
  27. holdSecs = 3
  28. )
  29. func main() {
  30. if os.Getenv(envChild) == "1" {
  31. runChild()
  32. return
  33. }
  34. runParent()
  35. }
  36. // runChild 打开数据库并持有锁 holdSecs 秒, 然后关闭释放。
  37. func runChild() {
  38. path := os.Getenv(envDB)
  39. db, err := core.Open(path)
  40. if err != nil {
  41. fmt.Fprintln(os.Stderr, "child open:", err)
  42. os.Exit(2)
  43. }
  44. time.Sleep(holdSecs * time.Second) // 持有锁, 模拟正在使用
  45. _ = db.Close()
  46. }
  47. func runParent() {
  48. dir, err := os.MkdirTemp("", "filelock-")
  49. if err != nil {
  50. panic(err)
  51. }
  52. defer os.RemoveAll(dir)
  53. path := filepath.Join(dir, "test.db")
  54. // 先建库
  55. initDB, err := core.Open(path)
  56. if err != nil {
  57. panic(err)
  58. }
  59. _ = initDB.Close()
  60. exe, err := os.Executable()
  61. if err != nil {
  62. panic(err)
  63. }
  64. cmd := exec.Command(exe)
  65. cmd.Env = append(os.Environ(), envChild+"=1", envDB+"="+path)
  66. if err := cmd.Start(); err != nil {
  67. panic(err)
  68. }
  69. pass, fail := 0, 0
  70. check := func(name string, ok bool) {
  71. if ok {
  72. fmt.Printf(" [OK] %s\n", name)
  73. pass++
  74. } else {
  75. fmt.Printf(" [FAIL] %s\n", name)
  76. fail++
  77. }
  78. }
  79. // 等子进程打开并持有锁
  80. time.Sleep(1000 * time.Millisecond)
  81. // 1. 子进程持锁时, 父进程 Open 必须被拒绝
  82. _, err = core.Open(path)
  83. check(fmt.Sprintf("另一进程持锁时 Open 被拒绝 (err=%v)", err),
  84. errors.Is(err, core.ErrDatabaseLocked))
  85. // 2. 等子进程退出释放锁
  86. _ = cmd.Wait()
  87. // 3. 锁释放后父进程 Open 必须成功
  88. db2, err2 := core.Open(path)
  89. check(fmt.Sprintf("锁释放后 Open 成功 (err=%v)", err2), err2 == nil && db2 != nil)
  90. if db2 != nil {
  91. _ = db2.Close()
  92. }
  93. fmt.Printf("\n总计: 通过 %d, 失败 %d\n", pass, fail)
  94. if fail > 0 {
  95. os.Exit(1)
  96. }
  97. }