{"id":"76e6c79b0e56d90a","repo":"gofiber/fiber","slug":"limiter-failed-to-persist-state-w-76e6c7","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_sliding.go","lineNumber":93,"sourceCode":"\n\t\t// Calculate how many hits can be made based on the current rate\n\t\tremaining := maxRequests - rate\n\n\t\t// Update storage. Garbage collect when the next window ends.\n\t\t// |--------------------------|--------------------------|\n\t\t//               ^            ^               ^          ^\n\t\t//              ts         e.exp   End sample window   End next window\n\t\t//               <------------>\n\t\t// \t\t\t\t   Reset In Sec\n\t\t// resetInSec = e.exp - ts - time until end of current window.\n\t\t// duration + expiration = end of next window.\n\t\t// Because we don't want to garbage collect in the middle of a window\n\t\t// we add the expiration to the duration.\n\t\t// Otherwise, after the end of \"sample window\", attackers could launch\n\t\t// a new request with the full window length.\n\t\tif setErr := manager.set(reqCtx, key, e, ttlDuration(resetInSec, expiration)); 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 allowed maximum for this request\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":75,"sourceCodeEnd":111,"githubUrl":"https://github.com/gofiber/fiber/blob/9a4c7e57fe0b080a04235d28a4b0d2b4b353d58c/middleware/limiter/limiter_sliding.go#L75-L111","documentation":"Returned by SlidingWindow.New's handler (limiter_sliding.go:91-94) when manager.set fails to persist the sliding-window counters (currHits, prevHits, exp) after incrementing currHits on the request path. Storage write or marshal failure.","triggerScenarios":"Configured remote Storage errors on SetWithContext, or the item MarshalMsg fails (codegen drift). The TTL passed is ttlDuration(resetInSec, expiration) which can be math.MaxInt64 on overflow — a storage that rejects very large TTLs could fail here.","commonSituations":"Redis unreachable mid-request; storage rejects the computed TTL; msgp-generated code out of sync; storage pool saturation under bursty traffic.","solutions":["Verify Storage health and that it accepts the TTL range produced by ttlDuration (which can be very large on overflow).","Run `make generate` so item MarshalMsg matches the struct.","If TTL overflow is plausible (very large ExpirationFunc), clamp the value upstream.","Wrap the limiter in a fail-open handler if a storage blip should not 500 the client."],"exampleFix":"// before\napp.Use(limiter.New(limiter.Config{Storage: redis.New()}))\n\n// after — bounded expiration + fail-open\nstore := redis.New()\napp.Use(func(c fiber.Ctx) error {\n    if err := sliding.New(&limiter.Config{\n        Storage: store,\n        ExpirationFunc: func(c fiber.Ctx) time.Duration { return time.Minute },\n    })(c); err != nil {\n        log.Printf(\"limiter persist failed: %v\", err)\n        return c.Next()\n    }\n    return nil\n})","handlingStrategy":"fallback","validationCode":"// sanity check that ttlDuration outputs are accepted by your storage\n// e.g. Redis accepts up to ~292 years; very large but valid","typeGuard":null,"tryCatchPattern":"// fail open around the limiter handler so storage blips do not 500 the client","preventionTips":["Clamp ExpirationFunc outputs to sensible windows to avoid TTL overflow.","Run `make generate` for manager_msgp.go consistency.","Use a storage driver that accepts the full TTL range produced by ttlDuration."],"tags":["limiter","sliding-window","storage","write"],"analyzedSha":"9a4c7e57fe0b080a04235d28a4b0d2b4b353d58c","analyzedAt":"2026-08-04T21:44:03.395Z","schemaVersion":2}