2
0
mirror of https://github.com/hibiken/asynq.git synced 2025-10-21 09:36:12 +08:00

use sorted-set to release stale locks

This commit is contained in:
ajatprabha
2021-10-30 16:07:46 +05:30
parent cb720fc10b
commit 34cc2d8081
8 changed files with 283 additions and 54 deletions

View File

@@ -4,9 +4,11 @@ import (
"context"
"fmt"
"strings"
"time"
"github.com/go-redis/redis/v8"
"github.com/hibiken/asynq"
asynqcontext "github.com/hibiken/asynq/internal/context"
)
// NewSemaphore creates a new counting Semaphore.
@@ -38,28 +40,73 @@ type Semaphore struct {
name string
}
// KEYS[1] -> asynq:sema:<scope>
// ARGV[1] -> max concurrency
// ARGV[2] -> current time in unix time
// ARGV[3] -> deadline in unix time
// ARGV[4] -> task ID
var acquireCmd = redis.NewScript(`
local lockCount = tonumber(redis.call('GET', KEYS[1]))
redis.call("ZREMRANGEBYSCORE", KEYS[1], "-inf", tonumber(ARGV[2])-1)
local lockCount = redis.call("ZCARD", KEYS[1])
if (not lockCount or lockCount < tonumber(ARGV[1])) then
redis.call('INCR', KEYS[1])
return true
if (lockCount < tonumber(ARGV[1])) then
redis.call("ZADD", KEYS[1], ARGV[3], ARGV[4])
return true
else
return false
return false
end
`)
// Acquire will try to acquire lock on the counting semaphore.
// - Returns true, iff semaphore key exists and current value is less than maxConcurrency
// - Returns false otherwise
func (s *Semaphore) Acquire(ctx context.Context) bool {
val, _ := acquireCmd.Run(ctx, s.rc, []string{semaphoreKey(s.name)}, []interface{}{s.maxConcurrency}...).Bool()
return val
// - Returns (true, nil), iff semaphore key exists and current value is less than maxConcurrency
// - Returns (false, nil) when lock cannot be acquired
// - Returns (false, error) otherwise
//
// The context.Context passed to Acquire must have a deadline set,
// this ensures that lock is released if the job goroutine crashes and does not call Release.
func (s *Semaphore) Acquire(ctx context.Context) (bool, error) {
d, ok := ctx.Deadline()
if !ok {
return false, fmt.Errorf("provided context must have a deadline")
}
taskID, ok := asynqcontext.GetTaskID(ctx)
if !ok {
return false, fmt.Errorf("provided context is missing task ID value")
}
b, err := acquireCmd.Run(ctx, s.rc,
[]string{semaphoreKey(s.name)},
[]interface{}{
s.maxConcurrency,
time.Now().Unix(),
d.Unix(),
taskID,
}...).Bool()
if err == redis.Nil {
return b, nil
}
return b, err
}
// Release will release the lock on the counting semaphore.
func (s *Semaphore) Release(ctx context.Context) {
s.rc.Decr(ctx, semaphoreKey(s.name))
func (s *Semaphore) Release(ctx context.Context) error {
taskID, ok := asynqcontext.GetTaskID(ctx)
if !ok {
return fmt.Errorf("provided context is missing task ID value")
}
n, err := s.rc.ZRem(ctx, semaphoreKey(s.name), taskID).Result()
if err != nil {
return fmt.Errorf("redis command failed: %v", err)
}
if n == 0 {
return fmt.Errorf("no lock found for task %q", taskID)
}
return nil
}
// Close closes the connection with redis.
@@ -68,5 +115,5 @@ func (s *Semaphore) Close() error {
}
func semaphoreKey(name string) string {
return fmt.Sprintf("asynq:sema:{%s}", name)
return fmt.Sprintf("asynq:sema:%s", name)
}