Skip to content

Commit

Permalink
enhance: keylock object background recycling
Browse files Browse the repository at this point in the history
Signed-off-by: SimFG <[email protected]>
  • Loading branch information
SimFG committed Dec 27, 2024
1 parent 4df444e commit a0bbcc8
Show file tree
Hide file tree
Showing 2 changed files with 67 additions and 12 deletions.
59 changes: 47 additions & 12 deletions pkg/util/lock/key_lock.go
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ package lock

import (
"sync"
"time"

"go.uber.org/zap"

Expand All @@ -33,8 +34,12 @@ func (m *RefLock) ref() {
m.refCounter++
}

func (m *RefLock) unref() {
m.refCounter--
func (m *RefLock) unref() bool {
if m.refCounter > 0 {
m.refCounter--
return true
}
return false

Check warning on line 42 in pkg/util/lock/key_lock.go

View check run for this annotation

Codecov / codecov/patch

pkg/util/lock/key_lock.go#L42

Added line #L42 was not covered by tests
}

func newRefLock() *RefLock {
Expand All @@ -46,17 +51,39 @@ func newRefLock() *RefLock {
}

type KeyLock[K comparable] struct {
keyLocksMutex sync.Mutex
refLocks map[K]*RefLock
keyLocksMutex sync.Mutex
refLocks map[K]*RefLock
backgroundGCInterval time.Duration
}

func NewKeyLock[K comparable]() *KeyLock[K] {
return NewKeyLockWithGCTime[K](5 * time.Second)
}

func NewKeyLockWithGCTime[K comparable](gcInterval time.Duration) *KeyLock[K] {
keyLock := KeyLock[K]{
refLocks: make(map[K]*RefLock),
refLocks: make(map[K]*RefLock),
backgroundGCInterval: gcInterval,
}
keyLock.StartGC()
return &keyLock
}

func (k *KeyLock[K]) StartGC() {
go func() {
gcTimer := time.NewTimer(k.backgroundGCInterval)
for range gcTimer.C {
k.keyLocksMutex.Lock()
for key, keyLock := range k.refLocks {
if keyLock.refCounter == 0 {
delete(k.refLocks, key)
}
}
k.keyLocksMutex.Unlock()
}
}()
}

func (k *KeyLock[K]) Lock(key K) {
k.keyLocksMutex.Lock()
// update the key map
Expand Down Expand Up @@ -84,9 +111,10 @@ func (k *KeyLock[K]) Unlock(lockedKey K) {
log.Warn("Unlocking non-existing key", zap.Any("key", lockedKey))
return
}
keyLock.unref()
if keyLock.refCounter == 0 {
delete(k.refLocks, lockedKey)
success := keyLock.unref()
if !success {
log.Warn("Unlocking non-locked key", zap.Any("key", lockedKey))
return

Check warning on line 117 in pkg/util/lock/key_lock.go

View check run for this annotation

Codecov / codecov/patch

pkg/util/lock/key_lock.go#L116-L117

Added lines #L116 - L117 were not covered by tests
}
keyLock.mutex.Unlock()
}
Expand Down Expand Up @@ -118,15 +146,22 @@ func (k *KeyLock[K]) RUnlock(lockedKey K) {
log.Warn("Unlocking non-existing key", zap.Any("key", lockedKey))
return
}
keyLock.unref()
if keyLock.refCounter == 0 {
delete(k.refLocks, lockedKey)
success := keyLock.unref()
if !success {
log.Warn("Unlocking non-locked key", zap.Any("key", lockedKey))
return

Check warning on line 152 in pkg/util/lock/key_lock.go

View check run for this annotation

Codecov / codecov/patch

pkg/util/lock/key_lock.go#L151-L152

Added lines #L151 - L152 were not covered by tests
}
keyLock.mutex.RUnlock()
}

func (k *KeyLock[K]) size() int {
k.keyLocksMutex.Lock()
defer k.keyLocksMutex.Unlock()
return len(k.refLocks)
s := 0
for _, keyLock := range k.refLocks {
if keyLock.refCounter > 0 {
s++
}
}
return s
}
20 changes: 20 additions & 0 deletions pkg/util/lock/key_lock_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -67,3 +67,23 @@ func TestKeyRLock(t *testing.T) {
wg.Wait()
assert.Equal(t, keyLock.size(), 0)
}

func TestNewKeyLock(t *testing.T) {
keyLock := NewKeyLockWithGCTime[string](time.Second)
keyLock.Lock("a")
keyLock.Lock("b")

keyLock.Unlock("a")
keyLock.Unlock("b")

assert.Equal(t, 0, keyLock.size())
keyLock.keyLocksMutex.Lock()
keyLen := len(keyLock.refLocks)
keyLock.keyLocksMutex.Unlock()
assert.Equal(t, 2, keyLen)
time.Sleep(2 * time.Second)
keyLock.keyLocksMutex.Lock()
keyLen = len(keyLock.refLocks)
keyLock.keyLocksMutex.Unlock()
assert.Equal(t, 0, keyLen)
}

0 comments on commit a0bbcc8

Please sign in to comment.