{"record":{"id":"8a1f0aa2d0e0bcb1","repo":"hibiken/asynq","slug":"provided-context-must-have-a-deadline","errorCode":null,"errorMessage":"provided context must have a deadline","messagePattern":"provided context must have a deadline","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"x/rate/semaphore.go","lineNumber":71,"sourceCode":"if (count < tonumber(ARGV[1])) then\n     redis.call(\"ZADD\", KEYS[1], ARGV[3], ARGV[4])\n     return 'true'\nelse\n     return 'false'\nend\n`)\n\n// Acquire attempts to acquire a token from the semaphore.\n// - Returns (true, nil), iff semaphore key exists and current value is less than maxTokens\n// - Returns (false, nil) when token cannot be acquired\n// - Returns (false, error) otherwise\n//\n// The context.Context passed to Acquire must have a deadline set,\n// this ensures that token is released if the job goroutine crashes and does not call Release.\nfunc (s *Semaphore) Acquire(ctx context.Context) (bool, error) {\n\td, ok := ctx.Deadline()\n\tif !ok {\n\t\treturn false, fmt.Errorf(\"provided context must have a deadline\")\n\t}\n\n\ttaskID, ok := asynqcontext.GetTaskID(ctx)\n\tif !ok {\n\t\treturn false, fmt.Errorf(\"provided context is missing task ID value\")\n\t}\n\n\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 {","sourceCodeStart":53,"sourceCodeEnd":89,"githubUrl":"https://github.com/hibiken/asynq/blob/d135f1439bee74e989b7f9b41ecd542cc87f024a/x/rate/semaphore.go#L53-L89","documentation":"Semaphore.Acquire in x/rate implements a Redis-based counting semaphore that relies on the task's deadline to expire stale tokens if a worker crashes without calling Release. It therefore requires the passed context to carry a deadline; a deadline-less context cannot guarantee token reclamation, so Acquire refuses it.","triggerScenarios":"Calling sem.Acquire(ctx) with context.Background(), context.TODO(), or any context created via context.WithCancel/WithoutCancel that lost the deadline; calling Acquire outside a task handler where asynq's deadline is not set.","commonSituations":"Testing the semaphore with context.Background(); reusing the context after wrapping with context.WithoutCancel; acquiring a rate-limit token in code that runs outside asynq's task processing pipeline.","solutions":["Derive the context from the task context with context.WithDeadline(parent, deadline) before Acquire.","Ensure you call Acquire from within a task handler using the ctx passed to it (asynq sets deadlines from the task's timeout/deadline).","If acquiring outside a task, explicitly set a sensible deadline matching your maximum processing time.","Set a per-task Timeout in the asynq task options so the handler context has a deadline."],"exampleFix":"// before\nok, err := sem.Acquire(context.Background())\n// after\nctx, cancel := context.WithTimeout(taskCtx, 30*time.Second)\ndefer cancel()\nok, err := sem.Acquire(ctx)","handlingStrategy":"validation","validationCode":"if _, ok := ctx.Deadline(); !ok {\n    return errors.New(\"semaphore.Acquire requires a context with deadline\")\n}","typeGuard":null,"tryCatchPattern":"ok, err := sem.Acquire(ctx)\nif err != nil {\n    if strings.Contains(err.Error(), \"deadline\") {\n        return fmt.Errorf(\"acquire: %w\", err)\n    }\n    return err\n}","preventionTips":["Always call Acquire with the task-handler context (asynq sets deadlines from task options).","Set a Timeout on every task (asynq.Timeout option) so handler contexts always have deadlines.","In non-task code, wrap with context.WithTimeout before Acquire.","Never pass context.Background()/TODO() to Acquire."],"tags":["go","asynq","rate-limiting","context","semaphore"],"backgroundTag":"missing-required-argument","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"}