{"record":{"id":"010e7efcf123fb1d","repo":"hibiken/asynq","slug":"rate-newsemaphore-unsupported-redisconnopt-type","errorCode":null,"errorMessage":"rate.NewSemaphore: unsupported RedisConnOpt type %T","messagePattern":"rate\\.NewSemaphore: unsupported RedisConnOpt type %T","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"error","filePath":"x/rate/semaphore.go","lineNumber":19,"sourceCode":"// Package rate contains rate limiting strategies for asynq.Handler(s).\npackage rate\n\nimport (\n\t\"context\"\n\t\"fmt\"\n\t\"strings\"\n\t\"time\"\n\n\t\"github.com/hibiken/asynq\"\n\tasynqcontext \"github.com/hibiken/asynq/internal/context\"\n\t\"github.com/redis/go-redis/v9\"\n)\n\n// NewSemaphore creates a counting Semaphore for the given scope with the given number of tokens.\nfunc NewSemaphore(rco asynq.RedisConnOpt, scope string, maxTokens int) *Semaphore {\n\trc, ok := rco.MakeRedisClient().(redis.UniversalClient)\n\tif !ok {\n\t\tpanic(fmt.Sprintf(\"rate.NewSemaphore: unsupported RedisConnOpt type %T\", rco))\n\t}\n\n\tif maxTokens < 1 {\n\t\tpanic(\"rate.NewSemaphore: maxTokens cannot be less than 1\")\n\t}\n\n\tif len(strings.TrimSpace(scope)) == 0 {\n\t\tpanic(\"rate.NewSemaphore: scope should not be empty\")\n\t}\n\n\treturn &Semaphore{\n\t\trc:        rc,\n\t\tscope:     scope,\n\t\tmaxTokens: maxTokens,\n\t}\n}\n\n// Semaphore is a distributed counting semaphore which can be used to set maxTokens across multiple asynq servers.","sourceCodeStart":1,"sourceCodeEnd":37,"githubUrl":"https://github.com/hibiken/asynq/blob/d135f1439bee74e989b7f9b41ecd542cc87f024a/x/rate/semaphore.go#L1-L37","documentation":"asynq's x/rate.NewSemaphore panics when the provided RedisConnOpt does not produce a client implementinging redis.UniversalClient via MakeRedisClient(). Only RedisClient, RedisClusterClient, and FailoverClient options are supported. Any other RedisConnOpt implementation is rejected at construction time.","triggerScenarios":"Calling rate.NewSemaphore(rco, scope, maxTokens) where rco is a custom RedisConnOpt implementation, a nil option, or a type whose MakeRedisClient() returns something other than a redis.UniversalClient (e.g. a plain redis.Client is fine, but a mock or wrapper is not).","commonSituations":"Passing a custom/mock RedisConnOpt in tests; using an older or third-party RedisConnOpt implementation; passing a nil or wrongly-typed option after refactoring; assuming any asynq.RedisConnOpt works with the x/rate package.","solutions":["Use a supported option: asynq.NewRedisClientOptimizer or asynq.ParseRedisURI(\"redis://...\") / asynq.RedisClientOpt{Addr: ...}.","If using a cluster, switch to asynq.RedisClusterClientOpt.","Verify rco.MakeRedisClient() returns a value implementing redis.UniversalClient before calling NewSemaphore.","In tests, use a real/miniredis-backed RedisClientOpt instead of a custom mock RedisConnOpt."],"exampleFix":"// before\ntype myOpt struct{}\nfunc (myOpt) MakeRedisClient() interface{} { return someCustomClient }\nsem := rate.NewSemaphore(myOpt{}, \"scope\", 10)\n// after\nrco := asynq.RedisClientOpt{Addr: \"localhost:6379\"}\nsem := rate.NewSemaphore(rco, \"my-scope\", 10)","handlingStrategy":"type-guard","validationCode":"func isSupportedOpt(rco asynq.RedisConnOpt) bool {\n    _, ok := rco.MakeRedisClient().(redis.UniversalClient)\n    return ok\n}","typeGuard":"func asUniversalClient(rco asynq.RedisConnOpt) (redis.UniversalClient, bool) {\n    c, ok := rco.MakeRedisClient().(redis.UniversalClient)\n    return c, ok\n}","tryCatchPattern":"// panics cannot be caught by try/catch in Go; guard before calling:\nif c, ok := asUniversalClient(rco); ok {\n    _ = rate.NewSemaphore(rco, scope, tokens)\n} else {\n    return fmt.Errorf(\"unsupported RedisConnOpt %T; use RedisClientOpt/RedisClusterClientOpt/FailoverClientOpt\", rco)\n}","preventionTips":["Only use asynq.RedisClientOpt, RedisClusterClientOpt, or FailoverClientOpt with x/rate.","Use asynq.ParseRedisURI for connection strings to get a supported option.","Avoid custom RedisConnOpt implementations or mocks with NewSemaphore.","Write a smoke test that constructs the semaphore with your production connection option."],"tags":["go","panic","redis","rate-limiting","type-mismatch"],"backgroundTag":"incompatible-source-type","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"}