package repository import ( "context" "time" "github.com/Wei-Shaw/sub2api/internal/service" "github.com/redis/go-redis/v9" ) const leaderLockKeyPrefix = "leader:lock:" // leaderLockReleaseScript releases a leader lock only when the caller still owns // it (compare-and-delete by owner token). This prevents a previous holder whose // lock already expired — and was re-acquired by another instance — from deleting // the new owner's lock. var leaderLockReleaseScript = redis.NewScript(` if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("DEL", KEYS[1]) end return 0 `) type leaderLockCache struct { rdb *redis.Client } // NewLeaderLockCache returns a Redis-backed implementation of // service.LeaderLockCache used by periodic background jobs to elect a single // runner across instances. func NewLeaderLockCache(rdb *redis.Client) service.LeaderLockCache { return &leaderLockCache{rdb: rdb} } func (c *leaderLockCache) TryAcquireLeaderLock(ctx context.Context, key, owner string, ttl time.Duration) (bool, error) { return c.rdb.SetNX(ctx, leaderLockKeyPrefix+key, owner, ttl).Result() } func (c *leaderLockCache) ReleaseLeaderLock(ctx context.Context, key, owner string) error { return leaderLockReleaseScript.Run(ctx, c.rdb, []string{leaderLockKeyPrefix + key}, owner).Err() }