package redisactivity import ( "context" "crypto/rand" _ "embed" "encoding/hex" "errors" "fmt" "time" "github.com/redis/go-redis/v9" extractionDomain "proxy-pool/internal/domain/extraction" ) type scriptStatus string const ( scriptOK scriptStatus = "ok" scriptInvalid scriptStatus = "invalid" scriptNotFound scriptStatus = "not_found" scriptConflict scriptStatus = "conflict" scriptStale scriptStatus = "stale" scriptUnavailable scriptStatus = "unavailable" scriptInsufficient scriptStatus = "insufficient" scriptAlreadyOwned scriptStatus = "already_owned" scriptNotDraining scriptStatus = "not_draining" scriptDrainNotReady scriptStatus = "drain_not_ready" ) type upsertScriptReply struct { Status scriptStatus `json:"status"` Accepted int `json:"accepted"` Inserted int `json:"inserted"` Refreshed int `json:"refreshed"` Dropped int `json:"dropped"` } type healthScriptReply struct { Status scriptStatus `json:"status"` Record string `json:"record,omitempty"` } type extractScriptReply struct { Status scriptStatus `json:"status"` RequestDigest string `json:"requestDigest"` Record string `json:"record,omitempty"` } type ownershipScriptReply struct { Status scriptStatus `json:"status"` Record string `json:"record,omitempty"` } type maintenanceScriptReply struct { Status scriptStatus `json:"status"` Count int `json:"count"` } //go:embed scripts/upsert.lua var upsertSource string //go:embed scripts/health.lua var healthSource string //go:embed scripts/extract.lua var extractSource string //go:embed scripts/ownership.lua var ownershipSource string //go:embed scripts/sweep.lua var sweepSource string var ( upsertScript = redis.NewScript(upsertSource) healthScript = redis.NewScript(healthSource) extractScript = redis.NewScript(extractSource) ownershipScript = redis.NewScript(ownershipSource) sweepScript = redis.NewScript(sweepSource) ) func runScript(ctx context.Context, client redis.Scripter, script *redis.Script, keys []string, args ...any) (any, error) { result, err := script.Run(ctx, client, keys, args...).Result() if err != nil { if errors.Is(err, context.Canceled) || errors.Is(err, context.DeadlineExceeded) { return nil, err } return nil, errors.Join( extractionDomain.ErrStoreUnavailable, fmt.Errorf("run redis activity script: %w", err), ) } return result, nil } func newOperationID() (string, error) { var value [16]byte if _, err := rand.Read(value[:]); err != nil { return "", fmt.Errorf("create redis activity operation ID: %w", err) } return hex.EncodeToString(value[:]), nil } func operationTTLMillis(ttl time.Duration) int64 { milliseconds := ttl / time.Millisecond if ttl%time.Millisecond != 0 { milliseconds++ } return int64(milliseconds) } func decodeScriptResult(result any, destination any) error { var payload string switch value := result.(type) { case string: payload = value case []byte: payload = string(value) default: return errors.Join(extractionDomain.ErrStoreUnavailable, errors.New("invalid Redis script reply type")) } if err := decodeJSON(payload, destination); err != nil { return errors.Join(extractionDomain.ErrStoreUnavailable, fmt.Errorf("decode Redis script reply: %w", err)) } return nil }