You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

118 lines
2.9 KiB

  1. package lock_manager
  2. import (
  3. "fmt"
  4. "github.com/google/uuid"
  5. "github.com/puzpuzpuz/xsync/v2"
  6. "time"
  7. )
  8. // LockManager lock manager
  9. type LockManager struct {
  10. locks *xsync.MapOf[string, *Lock]
  11. }
  12. type Lock struct {
  13. Token string
  14. ExpirationNs int64
  15. Key string // only used for moving locks
  16. }
  17. func NewLockManager() *LockManager {
  18. t := &LockManager{
  19. locks: xsync.NewMapOf[*Lock](),
  20. }
  21. go t.CleanUp()
  22. return t
  23. }
  24. func (lm *LockManager) Lock(path string, ttlDuration time.Duration, token string) (renewToken string, err error) {
  25. lm.locks.Compute(path, func(oldValue *Lock, loaded bool) (newValue *Lock, delete bool) {
  26. if oldValue != nil {
  27. now := time.Now()
  28. if oldValue.ExpirationNs > 0 && oldValue.ExpirationNs < now.UnixNano() {
  29. // lock is expired, set to a new lock
  30. expirationNs := time.Now().Add(ttlDuration).UnixNano()
  31. return &Lock{Token: token, ExpirationNs: expirationNs}, false
  32. }
  33. if oldValue.Token == token {
  34. expirationNs := time.Now().Add(ttlDuration).UnixNano()
  35. return &Lock{Token: token, ExpirationNs: expirationNs}, false
  36. } else {
  37. err = fmt.Errorf("lock: token mismatch")
  38. return oldValue, false
  39. }
  40. } else {
  41. expirationNs := time.Now().Add(ttlDuration).UnixNano()
  42. if token == "" {
  43. renewToken = uuid.New().String()
  44. return &Lock{Token: renewToken, ExpirationNs: expirationNs}, false
  45. } else {
  46. err = fmt.Errorf("lock: non-empty token on a new lock")
  47. return nil, false
  48. }
  49. return &Lock{Token: token, ExpirationNs: expirationNs}, false
  50. }
  51. })
  52. return
  53. }
  54. func (lm *LockManager) Unlock(path string, token string) (isUnlocked bool, err error) {
  55. lm.locks.Compute(path, func(oldValue *Lock, loaded bool) (newValue *Lock, delete bool) {
  56. if oldValue != nil {
  57. now := time.Now()
  58. if oldValue.ExpirationNs > 0 && oldValue.ExpirationNs < now.UnixNano() {
  59. // lock is expired, delete it
  60. isUnlocked = true
  61. return nil, true
  62. }
  63. if oldValue.Token == token {
  64. if oldValue.ExpirationNs <= now.UnixNano() {
  65. isUnlocked = true
  66. return nil, true
  67. }
  68. return oldValue, false
  69. } else {
  70. isUnlocked = false
  71. err = fmt.Errorf("unlock: token mismatch")
  72. return oldValue, false
  73. }
  74. } else {
  75. isUnlocked = true
  76. return nil, true
  77. }
  78. })
  79. return
  80. }
  81. func (lm *LockManager) CleanUp() {
  82. for {
  83. time.Sleep(1 * time.Minute)
  84. now := time.Now().UnixNano()
  85. lm.locks.Range(func(key string, value *Lock) bool {
  86. if now > value.ExpirationNs {
  87. lm.locks.Delete(key)
  88. return true
  89. }
  90. return true
  91. })
  92. }
  93. }
  94. // TakeOutLocksByKey takes out locks by key
  95. // if keyFn return true, the lock will be taken out
  96. func (lm *LockManager) TakeOutLocksByKey(keyFn func(key string) bool) (locks []*Lock) {
  97. now := time.Now().UnixNano()
  98. lm.locks.Range(func(key string, lock *Lock) bool {
  99. if now > lock.ExpirationNs {
  100. lm.locks.Delete(key)
  101. return true
  102. }
  103. if keyFn(key) {
  104. lm.locks.Delete(key)
  105. lock.Key = key
  106. locks = append(locks, lock)
  107. }
  108. return true
  109. })
  110. return
  111. }