{"record":{"id":"78be52bf40abf735","repo":"hibiken/asynq","slug":"redis-command-failed-w","errorCode":null,"errorMessage":"redis command failed: %w","messagePattern":"redis command failed: %w","errorType":"exception","errorClass":null,"httpStatus":null,"severity":"error","filePath":"x/rate/semaphore.go","lineNumber":97,"sourceCode":"\treturn acquireCmd.Run(ctx, s.rc,\n\t\t[]string{semaphoreKey(s.scope)},\n\t\ts.maxTokens,\n\t\ttime.Now().Unix(),\n\t\td.Unix(),\n\t\ttaskID,\n\t).Bool()\n}\n\n// Release will release the token on the counting semaphore.\nfunc (s *Semaphore) Release(ctx context.Context) error {\n\ttaskID, ok := asynqcontext.GetTaskID(ctx)\n\tif !ok {\n\t\treturn fmt.Errorf(\"provided context is missing task ID value\")\n\t}\n\n\tn, err := s.rc.ZRem(ctx, semaphoreKey(s.scope), taskID).Result()\n\tif err != nil {\n\t\treturn fmt.Errorf(\"redis command failed: %w\", err)\n\t}\n\n\tif n == 0 {\n\t\treturn fmt.Errorf(\"no token found for task %q\", taskID)\n\t}\n\n\treturn nil\n}\n\n// Close closes the connection to redis.\nfunc (s *Semaphore) Close() error {\n\treturn s.rc.Close()\n}\n\nfunc semaphoreKey(scope string) string {\n\treturn \"asynq:sema:\" + scope\n}\n","sourceCodeStart":79,"sourceCodeEnd":115,"githubUrl":"https://github.com/hibiken/asynq/blob/d135f1439bee74e989b7f9b41ecd542cc87f024a/x/rate/semaphore.go#L79-L115","documentation":"Semaphore.Release removes the caller's token from the Redis sorted set with ZRem. If the Redis command itself fails (connection error, timeout, read-only replica, cluster error), the error is wrapped as \"redis command failed\" and returned.","triggerScenarios":"Calling sem.Release(ctx) while Redis is unreachable, the connection pool is exhausted, the key is in a wrong cluster slot, or Redis returns any command-level error.","commonSituations":"Redis restart or failover mid-processing; network blips between app and Redis; connecting the Semaphore to a different/replica Redis instance than intended; AUTH required but not configured.","solutions":["Check Redis availability (redis-cli ping) and fix connection settings in RedisClientOpt.","Unwrap the %w cause to identify connection-refused vs timeout vs auth errors.","Add retry with backoff for Release on transient network errors, since failing to release stalls the semaphore slot.","Ensure the Semaphore and the asynq server use the same reachable Redis instance/database."],"exampleFix":"// before\nsem := NewSemaphore(rdb, \"scope\", 1)\n// rdb points at 127.0.0.1:6399 (down)\n// after\nsem := NewSemaphore(rdb, \"scope\", 1)\nif err := sem.Release(ctx); err != nil {\n    log.Printf(\"release failed: %v\", err) // inspect wrapped redis cause\n}","handlingStrategy":"retry","validationCode":"if err := rdb.Ping(ctx).Err(); err != nil {\n    log.Printf(\"redis unavailable: %v\", err)\n}","typeGuard":null,"tryCatchPattern":"if err := sem.Release(ctx); err != nil {\n    // retry a few times before giving up so the slot isn't leaked\n    for i := 0; i < 3; i++ {\n        time.Sleep(100*time.Millisecond)\n        if err = sem.Release(ctx); err == nil { break }\n    }\n    log.Printf(\"release failed after retries: %v\", err)\n}","preventionTips":["Configure RedisClientOpt with sane DialTimeout/ReadTimeout and retries.","Monitor Redis health; stale tokens are eventually reaped by deadline expiry, but minimize churn.","Point the Semaphore at the same Redis instance/DB the asynq server uses."],"tags":["go","asynq","redis","rate-limiting","semaphore"],"backgroundTag":"redis-command-failed","analyzedSha":"d135f1439bee74e989b7f9b41ecd542cc87f024a","analyzedAt":"2026-09-07T19:02:34.660Z","contentChangedAt":"2026-09-07T19:02:34.660Z","schemaVersion":2},"datasetVersion":"2026-09-16T09:17:16.951Z"}