1
0
mirror of https://github.com/duke-git/lancet.git synced 2026-02-04 12:52:28 +08:00

feat: add RWKeyedLocker

This commit is contained in:
dudaodong
2025-04-17 14:25:19 +08:00
parent f4dee28ebb
commit d7f3354b98
4 changed files with 232 additions and 61 deletions

View File

@@ -58,6 +58,7 @@ func (l *KeyedLocker[K]) Do(ctx context.Context, key K, fn func()) error {
} }
} }
// acquire tries to acquire a lock for the specified key.
func (l *KeyedLocker[K]) acquire(key K) *lockEntry { func (l *KeyedLocker[K]) acquire(key K) *lockEntry {
lock, _ := l.locks.LoadOrStore(key, &lockEntry{}) lock, _ := l.locks.LoadOrStore(key, &lockEntry{})
entry := lock.(*lockEntry) entry := lock.(*lockEntry)
@@ -70,6 +71,7 @@ func (l *KeyedLocker[K]) acquire(key K) *lockEntry {
return entry return entry
} }
// release releases the lock for the specified key.
func (l *KeyedLocker[K]) release(key K, entry *lockEntry, rawKey K) { func (l *KeyedLocker[K]) release(key K, entry *lockEntry, rawKey K) {
if atomic.AddInt32(&entry.ref, -1) == 0 { if atomic.AddInt32(&entry.ref, -1) == 0 {
entry.mu.Lock() entry.mu.Lock()
@@ -91,3 +93,101 @@ func (l *KeyedLocker[K]) release(key K, entry *lockEntry, rawKey K) {
} }
} }
} }
// RWKeyedLocker is a read-write version of KeyedLocker.
type RWKeyedLocker[K comparable] struct {
locks sync.Map
ttl time.Duration
}
type rwLockEntry struct {
mu sync.RWMutex
ref int32
timer atomic.Pointer[time.Timer]
}
// NewRWKeyedLocker creates a new RWKeyedLocker with the specified TTL for lock expiration.
// The TTL is used to automatically release locks that are no longer held.
func NewRWKeyedLocker[K comparable](ttl time.Duration) *RWKeyedLocker[K] {
return &RWKeyedLocker[K]{ttl: ttl}
}
// RLock acquires a read lock for the specified key and executes the provided function.
// It returns an error if the context is canceled before the function completes.
func (l *RWKeyedLocker[K]) RLock(ctx context.Context, key K, fn func()) error {
entry := l.acquire(key)
defer l.release(entry, key)
done := make(chan struct{})
go func() {
entry.mu.RLock()
defer entry.mu.RUnlock()
select {
case <-ctx.Done():
default:
fn()
}
close(done)
}()
select {
case <-ctx.Done():
return ctx.Err()
case <-done:
return nil
}
}
// Lock acquires a write lock for the specified key and executes the provided function.
// It returns an error if the context is canceled before the function completes.
func (l *RWKeyedLocker[K]) Lock(ctx context.Context, key K, fn func()) error {
entry := l.acquire(key)
defer l.release(entry, key)
done := make(chan struct{})
go func() {
entry.mu.Lock()
defer entry.mu.Unlock()
select {
case <-ctx.Done():
default:
fn()
}
close(done)
}()
select {
case <-ctx.Done():
return ctx.Err()
case <-done:
return nil
}
}
// acquire tries to acquire a read lock for the specified key.
func (l *RWKeyedLocker[K]) acquire(key K) *rwLockEntry {
actual, _ := l.locks.LoadOrStore(key, &rwLockEntry{})
entry := actual.(*rwLockEntry)
atomic.AddInt32(&entry.ref, 1)
if t := entry.timer.Swap(nil); t != nil {
t.Stop()
}
return entry
}
// release releases the lock for the specified key.
func (l *RWKeyedLocker[K]) release(entry *rwLockEntry, rawKey K) {
if atomic.AddInt32(&entry.ref, -1) == 0 {
timer := time.AfterFunc(l.ttl, func() {
if atomic.LoadInt32(&entry.ref) == 0 {
l.locks.Delete(rawKey)
}
})
entry.timer.Store(timer)
}
}

View File

@@ -104,3 +104,74 @@ func TestKeyedLocker_LockReleaseAfterTTL(t *testing.T) {
err = locker.Do(context.Background(), "ttl-key", func() {}) err = locker.Do(context.Background(), "ttl-key", func() {})
assert.IsNil(err) assert.IsNil(err)
} }
func TestRWKeyedLocker_LockAndUnlock(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestKeyedLocker_LockReleaseAfterTTL")
locker := NewRWKeyedLocker[string](500 * time.Millisecond)
var locked bool
err := locker.Lock(context.Background(), "key1", func() {
locked = true
})
assert.IsNil(err)
assert.Equal(true, locked)
}
func TestRWKeyedLocker_RLockParallel(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestKeyedLocker_LockReleaseAfterTTL")
locker := NewRWKeyedLocker[string](1 * time.Second)
var mu sync.Mutex
var count int
wg := sync.WaitGroup{}
for i := 0; i < 5; i++ {
wg.Add(1)
go func() {
defer wg.Done()
err := locker.RLock(context.Background(), "shared-key", func() {
time.Sleep(10 * time.Millisecond)
mu.Lock()
count++
mu.Unlock()
})
assert.IsNil(err)
}()
}
wg.Wait()
assert.Equal(5, count)
}
func TestRWKeyedLocker_LockTimeout(t *testing.T) {
t.Parallel()
assert := internal.NewAssert(t, "TestRWKeyedLocker_LockTimeout")
locker := NewRWKeyedLocker[string](1 * time.Second)
start := make(chan struct{})
go func() {
locker.Lock(context.Background(), "key-timeout", func() {
close(start)
time.Sleep(200 * time.Millisecond)
})
}()
<-start
ctx, cancel := context.WithTimeout(context.Background(), 50*time.Millisecond)
defer cancel()
err := locker.Lock(ctx, "key-timeout", func() {
t.Error("should not reach here")
})
assert.IsNotNil(err)
}

View File

@@ -1,51 +1,51 @@
-----BEGIN rsa private key----- -----BEGIN rsa private key-----
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
-----END rsa private key----- -----END rsa private key-----

View File

@@ -1,14 +1,14 @@
-----BEGIN rsa public key----- -----BEGIN rsa public key-----
MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAp8NkRHdZhqGYZ6tkWy3x MIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIICCgKCAgEAtKEukXkdl8ggX5I5Us0M
jkm7PlRqi2yS+AgguuuWhw3DvhM4BYwKt+r7Z0aEK9uIqpi2GwLv475kt+sQlPFb BcsrFzbTgwOn8u72IhJzruvsHcjlGGF5L1eXh3C0zbcoWq+Bxf8NnXvSXASZlZHT
W/qVEM2RE9X4kZFhc56ZfomeUq/puUd6Yz4Ba7JkXsk1ErwtMbzf+eNPlCA9idBZ iSVLFerxFw2LwTNcyfOz/Y/wIbtrYu4ZdttLKdJS607UvnZ57O7SQni5rxYAP5j+
Ofccy6nXHQkGsQQO9o8AM9jdOfhI8x/8CqJss2IZjQZxlzc37Rqs+OVD2S8S2Dr6 veKHEJgAO7Sn0lEEWwkeudsfVrQmQx6JHYtCtx78p2GHXPj9wXWHxWHKMkczKtmB
QUPyD/2P1dCwm4imw7BX3BMcwVg/LCQIGJIoMQ16T6prA/c6ShtnxHhc2ZyJ8ito FDLlYocPhQfWzx87sYKjGmTMGzVddsrllAqVozlxXTQndOdozx8BWHSBiYWguOvW
7Bs9v7+9yGd6w6Img/I0QCbFwGbXFSHLypFV2XauzFZbGQg7WM2WuKuU6NvRx1/E iYjvryrIf0B7wn4wt3o2Yre3w4BkwrTUE73JWG2q1NLuPoT27m7WcdVFGGcKAygA
I/DMXUIXRNvAlgXYbJc1V+x/2718MuuUaQjw39xderTkhU26wL2Rr/WfUaHQRq7F dMMSwS530v8tnAw876/w1SfUmtkIcqknysMjsvxHx1fbEfkl3b0yBnzcUrT1qxzm
alaPohpw9P5awm2dZGRo+mw+KmzWMf/Yb2h11oVBeeHK1H/FoDBR8tq519QT2dSn NEqJSQXBtFYFyGgyeL8J1ul/T5hPSNz0we5TO/xRkX5Y1HNJ+a2r8EnE1PU8zgQG
U4teIXqfT8KR40PZQ8IzIISyGoXi2uvlLIGrht6xweysyOAHHAaoDiX/ZeYDzhsd BxYSIub4ajSUccyn5yHQ/k2FlsolZiu/UzfnzMgdg55svgf5x1ntKYBoVHuYe84A
cRzo6fih+JPs/AwbeoNVuTBb0FZslEXmB+rWdlCABWyvKjXqFaBOw966muBoDpvl b3WRAeVlUi0OphvFlBzfIQvrG2sRbiPZRZZ0E8kBH94zPmrFmyX+7TwlSqVHtNFc
FhIXXT7cAhFN072yaDNa3judlY9yiBAWQQsv2Qr05YJIfdS3nQ3bqIu3BIwOhv/t +Ynz+hrHcd4z6N7fIb+D1gSYuukkcRqr4ANO0ugZk0YYKaWDf86H7OZOXna8ObIA
HCfUANM3DosKam4kxEIydWECAwEAAQ== i+3gx+Wcp1lm7Ffk9sn+dGkCAwEAAQ==
-----END rsa public key----- -----END rsa public key-----