{"id":"7e4b8b9ef59918e9","repo":"gofiber/fiber","slug":"limiter-failed-to-persist-state-w","errorCode":null,"errorMessage":"limiter: failed to persist state: %w","messagePattern":"limiter: failed to persist state: %w","errorType":"http","errorClass":null,"httpStatus":null,"severity":"error","filePath":"middleware/limiter/limiter_fixed.go","lineNumber":85,"sourceCode":"\t\t\t// Check if entry is expired\n\t\t\te.currHits = 0\n\t\t\te.exp = ts + expiration\n\t\t}\n\n\t\t// Increment hits\n\t\te.currHits++\n\n\t\t// Calculate when it resets in seconds\n\t\tresetInSec := e.exp - ts\n\t\twindowExpiresAt := e.exp\n\n\t\t// Set how many hits we have left\n\t\tremaining := maxRequests - e.currHits\n\n\t\t// Update storage\n\t\tif setErr := manager.set(reqCtx, key, e, expirationDuration); setErr != nil {\n\t\t\tmux.Unlock()\n\t\t\treturn fmt.Errorf(\"limiter: failed to persist state: %w\", setErr)\n\t\t}\n\n\t\t// Unlock entry\n\t\tmux.Unlock()\n\n\t\t// Check if hits exceed the max\n\t\tif remaining < 0 {\n\t\t\t// Return response with Retry-After header\n\t\t\t// https://tools.ietf.org/html/rfc6584\n\t\t\tif !cfg.DisableHeaders {\n\t\t\t\tc.Set(fiber.HeaderRetryAfter, utils.FormatUint(resetInSec))\n\t\t\t}\n\n\t\t\t// Call LimitReached handler\n\t\t\treturn cfg.LimitReached(c)\n\t\t}\n\n\t\t// Continue stack for reaching c.Response().StatusCode()","sourceCodeStart":67,"sourceCodeEnd":103,"githubUrl":"https://github.com/gofiber/fiber/blob/9a4c7e57fe0b080a04235d28a4b0d2b4b353d58c/middleware/limiter/limiter_fixed.go#L67-L103","documentation":"Returned by FixedWindow.New's handler (limiter_fixed.go:83-86) when manager.set fails to persist the counter after incrementing currHits on the request's first (or window-active) hit. Storage write failure or msgpack marshal failure inside manager.set.","triggerScenarios":"Configuring limiter with a remote Storage (Redis, etc.) that errors on SetWithContext, or a marshal failure of the item struct (see [173]-style codegen issue). On the in-memory default Storage this is unreachable.","commonSituations":"Redis briefly unreachable mid-request; storage pool exhausted; msgp-generated code out of sync with item struct; storage TLS misconfigured.","solutions":["Verify the configured Storage is reachable and has capacity.","Run `make generate` so item MarshalMsg matches the struct (manager_msgp.go).","If using in-memory only, confirm you did not pass a nil-but-typed Storage that errors.","Decide policy: a storage failure here returns the error to the client — wrap with a fallback handler if you prefer fail-open."],"exampleFix":"// before\napp.Use(limiter.New(limiter.Config{Storage: redis.New()}))\n\n// after — healthcheck + fail-open wrapper around limiter\nstore := redis.New()\nif err := store.Ping(); err != nil { log.Fatal(err) }\napp.Use(func(c fiber.Ctx) error {\n    if err := limiter.New(limiter.Config{Storage: store})(c); err != nil {\n        log.Printf(\"limiter down: %v — failing open\", err)\n        return c.Next()\n    }\n    return nil\n})","handlingStrategy":"fallback","validationCode":"// pre-flight: storage write + msgp codegen check\nctx, cancel := context.WithTimeout(context.Background(), time.Second)\ndefer cancel()\nif err := store.SetWithContext(ctx, \"__hc__\", []byte(\"ok\"), time.Minute); err != nil {\n    log.Fatal(err)\n}","typeGuard":null,"tryCatchPattern":"// fail open: rate limiting is best-effort for many services\napp.Use(func(c fiber.Ctx) error {\n    if err := limiterHandler(c); err != nil {\n        log.Printf(\"limiter down: %v\", err)\n        return c.Next()\n    }\n    return nil\n})","preventionTips":["Keep `make generate` current after any change to the item struct.","Healthcheck Storage at startup.","Decide fail-open vs fail-closed explicitly for your security posture."],"tags":["limiter","fixed-window","storage","write"],"analyzedSha":"9a4c7e57fe0b080a04235d28a4b0d2b4b353d58c","analyzedAt":"2026-08-04T21:44:03.395Z","schemaVersion":2}