package service import ( "context" "crypto/rand" "encoding/hex" "fmt" "os" "time" "github.com/go-redis/redis/v8" ) const ( worksmobileRelayLeaderLockKey = "baron:worksmobile:relay:leader" worksmobileRelayLeaderLockTTL = 30 * time.Second ) const worksmobileRelayLeaderRenewScript = ` if redis.call("GET", KEYS[1]) == ARGV[1] then return redis.call("EXPIRE", KEYS[1], ARGV[2]) end return 0 ` type WorksmobileRedisRelayLeaderLock struct { client *redis.Client key string ttl time.Duration ownerID string } func NewWorksmobileRedisRelayLeaderLock(redisService *RedisService) *WorksmobileRedisRelayLeaderLock { if redisService == nil || redisService.Client == nil { return nil } return &WorksmobileRedisRelayLeaderLock{ client: redisService.Client, key: worksmobileRelayLeaderLockKey, ttl: worksmobileRelayLeaderLockTTL, ownerID: newWorksmobileRelayLeaderOwnerID(), } } func (l *WorksmobileRedisRelayLeaderLock) EnsureLeadership(ctx context.Context) (bool, error) { if l == nil || l.client == nil { return true, nil } acquired, err := l.client.SetNX(ctx, l.key, l.ownerID, l.ttl).Result() if err != nil { return false, err } if acquired { return true, nil } ttlSeconds := int64(l.ttl / time.Second) if ttlSeconds <= 0 { ttlSeconds = 30 } result, err := l.client.Eval(ctx, worksmobileRelayLeaderRenewScript, []string{l.key}, l.ownerID, ttlSeconds).Int() if err != nil { return false, err } return result == 1, nil } func newWorksmobileRelayLeaderOwnerID() string { hostname, _ := os.Hostname() if hostname == "" { hostname = "unknown-host" } randomBytes := make([]byte, 8) if _, err := rand.Read(randomBytes); err != nil { return fmt.Sprintf("%s:%d:%d", hostname, os.Getpid(), time.Now().UnixNano()) } return fmt.Sprintf("%s:%d:%s", hostname, os.Getpid(), hex.EncodeToString(randomBytes)) }