Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Locker: add option function locker with time to live #50

Merged
merged 4 commits into from
Nov 11, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
145 changes: 122 additions & 23 deletions candiutils/locker.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@
import (
"context"
"errors"
"fmt"
"time"

"github.com/gomodule/redigo/redis"
Expand All @@ -13,72 +14,161 @@
type (
// RedisLocker lock using redis
RedisLocker struct {
pool *redis.Pool
pool *redis.Pool
lockeroptions LockerOptions
}

// NoopLocker empty locker
NoopLocker struct{}

// Options for RedisLocker
LockerOptions struct {
Prefix string
TTL time.Duration
}

// Option function type for setting options
LockerOption func(*LockerOptions)
)

// WithPrefix sets the prefix for keys
func WithPrefixLocker(prefix string) LockerOption {
return func(o *LockerOptions) {
o.Prefix = prefix
}

Check warning on line 38 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L35-L38

Added lines #L35 - L38 were not covered by tests
}

// WithTTL sets the default TTL for keys
func WithTTLLocker(ttl time.Duration) LockerOption {
return func(o *LockerOptions) {
o.TTL = ttl
}

Check warning on line 45 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L42-L45

Added lines #L42 - L45 were not covered by tests
}

// NewRedisLocker constructor
func NewRedisLocker(pool *redis.Pool) *RedisLocker {
return &RedisLocker{pool: pool}
func NewRedisLocker(pool *redis.Pool, opts ...LockerOption) *RedisLocker {
lockeroptions := LockerOptions{
Prefix: "LOCKFOR",
TTL: 0,
}
for _, opt := range opts {
opt(&lockeroptions)
}
return &RedisLocker{pool: pool, lockeroptions: lockeroptions}

Check warning on line 57 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L49-L57

Added lines #L49 - L57 were not covered by tests
}

// GetPrefix returns the prefix used for keys
func (r *RedisLocker) GetPrefixLocker() string {
return r.lockeroptions.Prefix + ":"

Check warning on line 62 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L61-L62

Added lines #L61 - L62 were not covered by tests
}

// GetTTLLocker returns the default TTL for keys
func (r *RedisLocker) GetTTLLocker() time.Duration {
return r.lockeroptions.TTL

Check warning on line 67 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L66-L67

Added lines #L66 - L67 were not covered by tests
}

func (r *RedisLocker) IsLocked(key string) bool {
conn := r.pool.Get()
incr, _ := redis.Int64(conn.Do("INCR", key))
conn.Close()
defer conn.Close()

lockKey := fmt.Sprintf("%s:%s", r.lockeroptions.Prefix, key)
incr, err := redis.Int64(conn.Do("INCR", lockKey))
if err != nil {
return false
}

Check warning on line 78 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L72-L78

Added lines #L72 - L78 were not covered by tests

return incr > 1

Check warning on line 80 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L80

Added line #L80 was not covered by tests
}

func (r *RedisLocker) IsLockedTTL(key string, TTL time.Duration) bool {
conn := r.pool.Get()
defer conn.Close()

lockKey := fmt.Sprintf("%s:%s", r.lockeroptions.Prefix, key)
incr, err := redis.Int64(conn.Do("INCR", lockKey))
if err != nil {
return false
}

Check warning on line 91 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L83-L91

Added lines #L83 - L91 were not covered by tests

var expireTime time.Duration
if TTL > 0 {
expireTime = TTL
} else {
expireTime = r.lockeroptions.TTL
}

Check warning on line 98 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L93-L98

Added lines #L93 - L98 were not covered by tests

if expireTime > 0 {
conn.Do("EXPIRE", lockKey, int(expireTime.Seconds()))
}

Check warning on line 102 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L100-L102

Added lines #L100 - L102 were not covered by tests

return incr > 1
}

func (r *RedisLocker) HasBeenLocked(key string) bool {
conn := r.pool.Get()
incr, _ := redis.Int64(conn.Do("GET", key))
conn.Close()
defer conn.Close()

lockKey := fmt.Sprintf("%s:%s", r.lockeroptions.Prefix, key)
incr, _ := redis.Int64(conn.Do("GET", lockKey))

Check warning on line 112 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L109-L112

Added lines #L109 - L112 were not covered by tests

return incr > 0
}

// Unlock method
func (r *RedisLocker) Unlock(key string) {
conn := r.pool.Get()
conn.Do("DEL", key)
conn.Close()
defer conn.Close()

lockKey := fmt.Sprintf("%s:%s", r.lockeroptions.Prefix, key)
conn.Do("DEL", lockKey)

Check warning on line 123 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L120-L123

Added lines #L120 - L123 were not covered by tests
}

// Reset method
func (r *RedisLocker) Reset(key string) {
conn := r.pool.Get()
keys, _ := redis.Strings(conn.Do("KEYS", key))
defer conn.Close()

lockKey := fmt.Sprintf("%s:%s", r.lockeroptions.Prefix, key)
keys, err := redis.Strings(conn.Do("KEYS", lockKey))
if err != nil {
fmt.Println("Error when reset locker: ", key, err)
return
}

Check warning on line 136 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L129-L136

Added lines #L129 - L136 were not covered by tests

for _, k := range keys {
conn.Do("DEL", k)
_, err := conn.Do("DEL", k)
if err != nil {
fmt.Println("Error when reset locker: ", key, err)
}

Check warning on line 142 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L139-L142

Added lines #L139 - L142 were not covered by tests
}
conn.Close()
return
}

// Disconnect close and reset
func (r *RedisLocker) Disconnect(ctx context.Context) error {
conn := r.pool.Get()
conn.Do("DEL", "LOCKFOR:*")
conn.Close()
defer conn.Close()

lockKey := fmt.Sprintf("%s:*", r.lockeroptions.Prefix)
_, err := conn.Do("DEL", lockKey)
if err != nil {
return err
}

Check warning on line 155 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L149-L155

Added lines #L149 - L155 were not covered by tests

return nil
}

// Lock method
func (r *RedisLocker) Lock(key string, timeout time.Duration) (unlockFunc func(), err error) {
if timeout <= 0 {
return func() {}, errors.New("Timeout must be positive")
return func() {}, errors.New("timeout must be positive")

Check warning on line 163 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L163

Added line #L163 was not covered by tests
}
if key == "" {
return func() {}, errors.New("Key cannot empty")
return func() {}, errors.New("key cannot empty")

Check warning on line 166 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L166

Added line #L166 was not covered by tests
}

lockKey := "LOCKFOR:" + key
unlockFunc = func() { r.Unlock(lockKey) }
if !r.IsLocked(lockKey) {
lockKey := fmt.Sprintf("%s:%s", r.lockeroptions.Prefix, key)
unlockFunc = func() { r.Unlock(key) }
if !r.IsLocked(key) {

Check warning on line 171 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L169-L171

Added lines #L169 - L171 were not covered by tests
return unlockFunc, nil
}

Expand All @@ -95,7 +185,7 @@
for {
switch msg := psc.Receive().(type) {
case redis.Message:
if msg.Pattern == eventChannel && lockKey == string(msg.Data) && !r.IsLocked(lockKey) {
if msg.Pattern == eventChannel && lockKey == string(msg.Data) && !r.IsLocked(key) {

Check warning on line 188 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L188

Added line #L188 was not covered by tests
wait <- nil
return
}
Expand All @@ -112,8 +202,8 @@
return unlockFunc, err

case <-time.After(timeout):
r.Unlock(lockKey)
return unlockFunc, errors.New("Timeout when waiting unlock another process")
r.Unlock(key)
return unlockFunc, errors.New("timeout when waiting unlock another process")

Check warning on line 206 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L205-L206

Added lines #L205 - L206 were not covered by tests
}
}

Expand All @@ -122,6 +212,9 @@
// IsLocked method
func (NoopLocker) IsLocked(string) bool { return false }

// IsLockedTTL method
func (NoopLocker) IsLockedTTL(string, time.Duration) bool { return false }

Check warning on line 216 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L216

Added line #L216 was not covered by tests

// HasBeenLocked method
func (NoopLocker) HasBeenLocked(string) bool { return false }

Expand All @@ -135,3 +228,9 @@
func (NoopLocker) Lock(string, time.Duration) (func(), error) { return func() {}, nil }

func (NoopLocker) Disconnect(context.Context) error { return nil }

// GetPrefix method
func (NoopLocker) GetPrefixLocker() string { return "" }

Check warning on line 233 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L233

Added line #L233 was not covered by tests

// GetTTLLocker method
func (NoopLocker) GetTTLLocker() time.Duration { return 0 }

Check warning on line 236 in candiutils/locker.go

View check run for this annotation

Codecov / codecov/patch

candiutils/locker.go#L236

Added line #L236 was not covered by tests
3 changes: 3 additions & 0 deletions codebase/interfaces/locker.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,10 +6,13 @@ type (
// Locker abstraction, lock concurrent process
Locker interface {
IsLocked(key string) bool
IsLockedTTL(key string, ttl time.Duration) bool
HasBeenLocked(key string) bool
Unlock(key string)
Reset(key string)
Lock(key string, timeout time.Duration) (unlockFunc func(), err error)
GetPrefixLocker() string
GetTTLLocker() time.Duration
Closer
}
)
2 changes: 1 addition & 1 deletion init.go
Original file line number Diff line number Diff line change
Expand Up @@ -2,5 +2,5 @@ package candi

const (
// Version of this library
Version = "v1.18.1"
Version = "v1.18.2"
)
54 changes: 54 additions & 0 deletions mocks/codebase/interfaces/Locker.go

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

Loading