{"id":"b5993b2a7bc3fd56","repo":"gofiber/fiber","slug":"failed-to-lock-w","errorCode":null,"errorMessage":"failed to lock: %w","messagePattern":"failed to lock: %w","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"middleware/idempotency/idempotency.go","lineNumber":126,"sourceCode":"\t\tif c.Get(cfg.KeyHeader) == \"\" {\n\t\t\treturn c.Next()\n\t\t}\n\n\t\t// Validate key\n\t\tkey := utils.CopyString(c.Get(cfg.KeyHeader))\n\t\tif err := cfg.KeyHeaderValidate(key); err != nil {\n\t\t\treturn err\n\t\t}\n\n\t\t// First-pass: if the idempotency key is in the storage, get and return the response\n\t\tif ok, err := maybeWriteCachedResponse(c, key); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write cached response at fastpath: %w\", err)\n\t\t} else if ok {\n\t\t\treturn nil\n\t\t}\n\n\t\tif err := cfg.Lock.Lock(key); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to lock: %w\", err)\n\t\t}\n\t\tdefer func() {\n\t\t\tif err := cfg.Lock.Unlock(key); err != nil {\n\t\t\t\tlog.Errorf(\"[IDEMPOTENCY] failed to unlock key %q: %v\", maskKey(key), err)\n\t\t\t}\n\t\t}()\n\n\t\t// Lock acquired. If the idempotency key now is in the storage, get and return the response\n\t\tif ok, err := maybeWriteCachedResponse(c, key); err != nil {\n\t\t\treturn fmt.Errorf(\"failed to write cached response while locked: %w\", err)\n\t\t} else if ok {\n\t\t\treturn nil\n\t\t}\n\n\t\t// Execute the request handler\n\t\tif err := c.Next(); err != nil {\n\t\t\t// If the request handler returned an error, return it and skip idempotency\n\t\t\treturn err","sourceCodeStart":108,"sourceCodeEnd":144,"githubUrl":"https://github.com/gofiber/fiber/blob/9a4c7e57fe0b080a04235d28a4b0d2b4b353d58c/middleware/idempotency/idempotency.go#L108-L144","documentation":"Returned by the middleware (idempotency.go:125-126) when cfg.Lock.Lock(key) fails. The default Locker is an in-process MemoryLock which only fails on context cancellation; a custom distributed Locker (Redis, etcd) can fail on connection loss or lock-acquisition timeout.","triggerScenarios":"Using a distributed Locker whose Lock(ctx, key) returns an error — Redis down, lock acquisition deadline exceeded, locker client misconfigured. The default in-memory locker only fails if the request context is already cancelled.","commonSituations":"Multi-instance deployment with a Redis-backed lock; client disconnected (context cancelled) while waiting behind another request with the same idempotency key; lock TTL shorter than the handler runtime causing acquisition thrash.","solutions":["Verify the Locker backend is reachable and healthy.","Make sure the Locker's per-key acquisition timeout exceeds your worst-case handler latency.","Cancel-protected clients: have upstream retries re-send the same idempotency key after a context-cancellation.","Consider whether a failure to lock should fall through (skip idempotency) rather than 500."],"exampleFix":"// before\nLock: myRedisLock, // fails loudly on Redis blip\n\n// after — Lock that returns nil on transient outage so the request proceeds\nLock: &tolerantLock{inner: myRedisLock}","handlingStrategy":"fallback","validationCode":"// confirm distributed lock backend is up before wiring middleware\nlock := redis.New(...)\nif err := lock.Ping(); err != nil {\n    log.Fatalf(\"lock backend down: %v\", err)\n}","typeGuard":null,"tryCatchPattern":"// tolerate transient lock failures by proceeding without locking\n// (idempotency may double-execute on retry, but the request succeeds)","preventionTips":["Size the lock acquisition timeout to your worst-case handler latency.","Have clients retry with the SAME idempotency key after a context cancellation.","Decide explicitly whether lock failures should fail closed or fail open for your app."],"tags":["idempotency","lock","distributed","redis"],"analyzedSha":"9a4c7e57fe0b080a04235d28a4b0d2b4b353d58c","analyzedAt":"2026-08-04T21:44:03.395Z","schemaVersion":2}