🐛 bug: Fix and improvements for the sliding window Limiter#3893
🐛 bug: Fix and improvements for the sliding window Limiter#3893ReneWerner87 merged 7 commits intomainfrom
Conversation
|
Note Other AI code review bot(s) detectedCodeRabbit has detected other AI code review bot(s) in this pull request and will avoid duplicating their findings in the review comments. This may lead to a less comprehensive review. WalkthroughImplements sliding-window rotation and TTL helpers; preserves and shifts previous/current buckets on rotation; recalculates rate and remaining using per-request Changes
Sequence Diagram(s)sequenceDiagram
participant Client
participant Limiter
participant Storage
participant Handler
Client->>Limiter: HTTP request (key)
Limiter->>Storage: get(key)
Storage-->>Limiter: entry {ts, prevHits, currHits, exp}
note right of Limiter `#f0f4c3`: rotateWindow(now, expiration)\ncompute resetInSec, weight, expire/shift prev→curr
Limiter->>Limiter: maxRequests := cfg.MaxFunc(c)\nrate := int(prevHits*weight)+currHits\nremaining := maxRequests - rate
alt Allowed
Limiter->>Storage: set(entry with currHits+1, ttlDuration(resetInSec, expiration))
Limiter->>Handler: call next()
Handler-->>Limiter: response (status)
alt Should Skip Hit (SkipFailed/SkipSuccessful)
note right of Limiter `#e8f5e9`: Use original entry reference\nrecompute weight/rate/remaining\ndecrement correct bucket\nLimiter->>Storage: set(updated entry, ttlDuration(...))
end
Limiter-->>Client: Response + X-RateLimit-* headers (unless disabled)
else LimitReached
Limiter-->>Client: 429 + Retry-After (resetInSec)
end
Estimated code review effort🎯 4 (Complex) | ⏱️ ~45 minutes
Suggested reviewers
Poem
Pre-merge checks and finishing touches❌ Failed checks (2 warnings)
✅ Passed checks (3 passed)
✨ Finishing touches
🧪 Generate unit tests (beta)
Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out. Comment |
Summary of ChangesHello @gaby, I'm Gemini Code Assist1! I'm currently reviewing this pull request and will post my feedback shortly. In the meantime, here's a summary to help you and other reviewers quickly get up to speed! This pull request addresses an issue in the sliding window rate limiter where it did not fully respect dynamically configured maximum request limits. The changes ensure that the limiter consistently uses the Highlights
Using Gemini Code AssistThe full guide for Gemini Code Assist can be found on our documentation page, here are some quick tips. Invoking Gemini You can request assistance from Gemini at any point by creating a comment using either
Customization To customize Gemini Code Assist for GitHub experience, repository maintainers can create a configuration file and/or provide a custom code review style guide (such as PEP-8 for Python) by creating and adding files to a Limitations & Feedback Gemini Code Assist may make mistakes. Please leave feedback on any instances where its feedback is incorrect or counter productive. You can react with 👍 and 👎 on @gemini-code-assist comments. If you're interested in giving your feedback about your experience with Gemini Code Assist for Github and other Google products, sign up here. You can also get AI-powered code generation, chat, as well as code reviews directly in the IDE at no cost with the Gemini Code Assist IDE Extension. Footnotes
|
Codecov Report❌ Patch coverage is
Additional details and impacted files@@ Coverage Diff @@
## main #3893 +/- ##
==========================================
- Coverage 91.69% 91.68% -0.02%
==========================================
Files 115 117 +2
Lines 9819 10030 +211
==========================================
+ Hits 9004 9196 +192
- Misses 516 530 +14
- Partials 299 304 +5
Flags with carried forward coverage won't be shown. Click here to find out more. ☔ View full report in Codecov by Sentry. 🚀 New features to boost your workflow:
|
There was a problem hiding this comment.
Code Review
This pull request effectively addresses the issue of the sliding window limiter not respecting the dynamic MaxFunc. The changes correctly use the dynamic maxRequests value for calculating the remaining capacity. The logic for handling skipped requests is also properly updated to recalculate the rate, which is a significant improvement over the previous implementation. The addition of a regression test is excellent, as it clearly verifies the intended behavior. I have one suggestion to further improve the accuracy of the rate calculation when requests are skipped.
There was a problem hiding this comment.
Pull request overview
This PR fixes a bug in the sliding window rate limiter where it was using the static cfg.Max value instead of respecting the dynamic MaxFunc value when calculating remaining capacity. The fix ensures that both the initial capacity check and the recalculation after skipped requests properly use the dynamic maximum.
Key Changes
- Updated the sliding window limiter to use
maxRequests(derived fromMaxFunc) instead ofcfg.Maxfor capacity calculations - Added logic to recompute
rateandremainingafter decrementing hits for skipped requests, ensuring headers reflect accurate values - Added a regression test that verifies
MaxFuncproperly overrides the staticMaxvalue in both enforcement and response headers
Reviewed changes
Copilot reviewed 2 out of 2 changed files in this pull request and generated 2 comments.
| File | Description |
|---|---|
| middleware/limiter/limiter_sliding.go | Fixed capacity calculation to use dynamic maxRequests instead of static cfg.Max, and added recalculation of rate/remaining after skipped requests |
| middleware/limiter/limiter_test.go | Added regression test that verifies MaxFunc overrides static Max value with proper enforcement and header values |
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (2)
middleware/limiter/limiter_sliding.go (1)
139-166: Recomputingrate/remainingafter skipped requests keeps headers accurate (optionally clamp for safety)Recomputing
rateandremainingaftere.currHits--in the skip branch ensures thatX-RateLimit-Remainingreflects the decremented hit count instead of the pre-skip value, which aligns with the intent ofSkip*Requests. In very concurrent scenarios, the recomputedremainingused only for headers could theoretically go negative due to other requests updating the same key; if you want to guard against that, you could optionally clamp just for header output, e.g.if remaining < 0 { remaining = 0 }, without changing enforcement logic (which still happens beforec.Next).middleware/limiter/limiter_test.go (1)
9-9: Regression test cleanly covers dynamic MaxFunc overriding static MaxThe added
strconvimport andTest_Limiter_Sliding_MaxFuncOverridesStaticMaxlook solid:
MaxFuncreturning a smallerdynamicMaxthanstaticMaxverifies that SlidingWindow enforcement andX-RateLimit-Limit/X-RateLimit-Remainingare governed by the dynamic value as intended.- The 3-request sequence (OK/OK/429) matches the sliding-window math with
dynamicMax == 2, and the header assertions ensure the remaining value is also computed fromdynamicMax.- Use of
requirefromtestifyis consistent with the project’s testing style. Based on learnings, this matches existing conventions.If you want to match surrounding tests, you could optionally add
t.Parallel()at the start of this test, but it’s not required.Also applies to: 414-446
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
middleware/limiter/limiter_sliding.go(3 hunks)middleware/limiter/limiter_test.go(2 hunks)
🧰 Additional context used
🧠 Learnings (4)
📚 Learning: 2025-10-16T07:19:52.418Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:19:52.418Z
Learning: In the Fiber codebase, the linter does not allow `require` assertions from within HTTP handlers (including net/http-style handlers). Use `t.Fatalf`, `t.Errorf`, or similar `testing.T` methods for error handling inside handler functions instead.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 2922
File: middleware/cors/utils.go:63-71
Timestamp: 2024-10-08T19:06:06.583Z
Learning: The project uses the testify/assert package for assertions in unit tests.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2024-10-02T23:03:31.727Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/store.go:164-167
Timestamp: 2024-10-02T23:03:31.727Z
Learning: Unit tests in this project use testify require.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2024-12-13T08:14:22.851Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: hooks_test.go:228-228
Timestamp: 2024-12-13T08:14:22.851Z
Learning: In Go test files, prefer using the `require` methods from the `testify` package for assertions instead of manual comparisons and calls to `t.Fatal` or `t.Fatalf`.
Applied to files:
middleware/limiter/limiter_test.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (6)
- GitHub Check: Upload results
- GitHub Check: Compare
- GitHub Check: lint
- GitHub Check: unit (1.25.x, windows-latest)
- GitHub Check: unit (1.25.x, macos-latest)
- GitHub Check: repeated
🔇 Additional comments (1)
middleware/limiter/limiter_sliding.go (1)
85-132: DynamicmaxRequestscorrectly drives remaining and limit enforcementUsing
remaining := maxRequests - rateand checkingif remaining < 0before callingcfg.LimitReachedcorrectly ties the effective limit and remaining capacity to the per-requestMaxFuncvalue, and keeps it consistent withX-RateLimit-Limit(which already usesmaxRequests). This fixes the previous mismatch where a staticcfg.Maxcould diverge from the dynamic limit while preserving the existing sliding-window math and storage semantics.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request aims to fix the sliding window limiter to respect a dynamic MaxFunc value, recompute allowances for skipped requests, and adds a regression test. The refactoring improves code structure and robustness, for example by extracting logic into rotateWindow and ttlDuration functions. However, I've identified two significant issues in the new sliding window logic that could lead to incorrect rate limiting. One is a race condition in handling skipped requests, and the other is a bug in the window rotation logic when multiple window periods have passed. I've provided detailed comments and suggestions for fixes for both issues.
There was a problem hiding this comment.
Actionable comments posted: 0
♻️ Duplicate comments (1)
middleware/limiter/limiter_test.go (1)
414-446: Align with existing test style: addt.Parallel()and ignore unused ctx inMaxFunc.Functionally this test looks good and covers the dynamic
MaxFuncvs staticMaxbehavior and headers. For consistency with the rest of this file:
- Call
t.Parallel()at the top of the test.- Change the
MaxFuncsignature to ignore the unusedfiber.Ctxparameter.For example:
func Test_Limiter_Sliding_MaxFuncOverridesStaticMax(t *testing.T) { - app := fiber.New() + t.Parallel() + app := fiber.New() @@ - app.Use(New(Config{ - Max: staticMax, - MaxFunc: func(fiber.Ctx) int { return dynamicMax }, + app.Use(New(Config{ + Max: staticMax, + MaxFunc: func(_ fiber.Ctx) int { + return dynamicMax + },
🧹 Nitpick comments (1)
middleware/limiter/limiter_sliding.go (1)
64-77: Sliding-window core logic looks correct; consider avoiding the secondmanager.getfor efficiency.The updated flow fixes the two main issues:
remainingis now derived from the dynamicmaxRequestsfromMaxFunc, and headers use the same value.- In the skip path you re-rotate the window, optionally decrement
currHits/prevHits, then recomputerateandremaininginstead of doing a simpleremaining++, which matches the sliding-window math.One thing to consider:
- After
c.Next(), you reacquire the lock and callmanager.get(reqCtx, key)again even thougheis already a pointer to the entry you just persisted. For in‑process stores this is an extra map lookup; for remote stores it adds an extra I/O round-trip per request. You can likely reuseedirectly in the second phase (still undermux.Lock()), just recomputingts,resetInSec,weight, and then applying the skip logic beforemanager.set. That would preserve the behavior while reducing storage calls and keeping the per-request logic centered on a single in-memory entry.Also applies to: 91-110, 118-120, 121-149
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
middleware/limiter/limiter_sliding.go(5 hunks)middleware/limiter/limiter_test.go(3 hunks)
🧰 Additional context used
🧠 Learnings (8)
📚 Learning: 2024-06-30T00:38:06.580Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3051
File: middleware/session/session.go:215-216
Timestamp: 2024-06-30T00:38:06.580Z
Learning: Parallel tests for `Session.Save` already exist in the `middleware/session/session_test.go` file, specifically in the `Test_Session_Save` and `Test_Session_Save_Expiration` functions.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2025-10-16T07:19:52.418Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:19:52.418Z
Learning: In the Fiber codebase, the linter does not allow `require` assertions from within HTTP handlers (including net/http-style handlers). Use `t.Fatalf`, `t.Errorf`, or similar `testing.T` methods for error handling inside handler functions instead.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2025-05-07T13:07:33.899Z
Learnt from: mdelapenya
Repo: gofiber/fiber PR: 3434
File: docs/api/services.md:39-43
Timestamp: 2025-05-07T13:07:33.899Z
Learning: When documenting Go interface methods in the Fiber project, avoid showing method signatures with the interface type as the receiver (e.g., `func (d *Service) Method()`) since interfaces cannot be used as receivers in Go. Instead, show just the method signature without a receiver or use a placeholder implementation name.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2024-12-13T08:14:22.851Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: hooks_test.go:228-228
Timestamp: 2024-12-13T08:14:22.851Z
Learning: In Go test files, prefer using the `require` methods from the `testify` package for assertions instead of manual comparisons and calls to `t.Fatal` or `t.Fatalf`.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2024-11-15T07:56:21.623Z
Learnt from: ReneWerner87
Repo: gofiber/fiber PR: 3161
File: app.go:923-932
Timestamp: 2024-11-15T07:56:21.623Z
Learning: In the Fiber framework, breaking changes are acceptable when moving from version 2 to version 3, including modifications to method signatures such as in the `Test` method in `app.go`.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 2922
File: middleware/cors/utils.go:63-71
Timestamp: 2024-10-08T19:06:06.583Z
Learning: The project uses the testify/assert package for assertions in unit tests.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2024-10-02T23:03:31.727Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/store.go:164-167
Timestamp: 2024-10-02T23:03:31.727Z
Learning: Unit tests in this project use testify require.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2024-10-12T10:01:44.206Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/middleware_test.go:190-191
Timestamp: 2024-10-12T10:01:44.206Z
Learning: When testing session `IdleTimeout` expiration, it's acceptable to use `time.Sleep` to simulate the passage of time in tests.
Applied to files:
middleware/limiter/limiter_test.go
🧬 Code graph analysis (2)
middleware/limiter/limiter_sliding.go (1)
constants.go (1)
StatusBadRequest(73-73)
middleware/limiter/limiter_test.go (2)
middleware/limiter/config.go (1)
Config(10-75)middleware/limiter/limiter_sliding.go (2)
SlidingWindow(15-15)SlidingWindow(18-160)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (4)
- GitHub Check: CodeQL analysis (go)
- GitHub Check: Agent
- GitHub Check: repeated
- GitHub Check: Compare
🔇 Additional comments (2)
middleware/limiter/limiter_test.go (1)
772-801: LGTM: solid coverage of sliding-window recomputation after handler delay.This test cleanly verifies that, with a delayed handler, the sliding window state and
X-RateLimit-*headers are recalculated based on the updated window, and it follows existing test patterns (t.Parallel, plain handler,requirein the test only). No changes needed.middleware/limiter/limiter_sliding.go (1)
162-215: Overflow-safe TTL helpers are a nice improvement.
rotateWindowcleanly encapsulates the per-entry window rotation and returnsresetInSec, and the newttlDuration/secondsToDurationhelpers correctly:
- Convert second counts to
time.Durationwith a bound (maxSeconds) to avoid overflow.- Saturate to
time.Duration(math.MaxInt64)both on conversion and on addition whenresetDuration + expirationDurationwould overflow.This keeps the effective TTL as “time until reset + one more window” while making the arithmetic robust for large expirations.
There was a problem hiding this comment.
Actionable comments posted: 1
♻️ Duplicate comments (1)
middleware/limiter/limiter_test.go (1)
9-9: Dynamic MaxFunc test looks good; addt.Parallel()and ignore the unused ctx paramThe new
Test_Limiter_Sliding_MaxFuncOverridesStaticMaxcorrectly asserts thatMaxFuncoverridesMaxand that the headers track the dynamic max. Two small consistency/style nits:
- Add
t.Parallel()at the start of the test, as with most other tests in this file.- Change the
MaxFuncsignature to ignore the unused parameter explicitly:-func Test_Limiter_Sliding_MaxFuncOverridesStaticMax(t *testing.T) { - app := fiber.New() +func Test_Limiter_Sliding_MaxFuncOverridesStaticMax(t *testing.T) { + t.Parallel() + app := fiber.New() @@ - app.Use(New(Config{ - Max: staticMax, - MaxFunc: func(fiber.Ctx) int { return dynamicMax }, + app.Use(New(Config{ + Max: staticMax, + MaxFunc: func(_ fiber.Ctx) int { return dynamicMax },Also applies to: 440-472
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (2)
middleware/limiter/limiter_sliding.go(5 hunks)middleware/limiter/limiter_test.go(4 hunks)
🧰 Additional context used
🧠 Learnings (9)
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/config.go:122-122
Timestamp: 2024-10-08T19:06:06.583Z
Learning: In `DefaultErrorHandler(c *fiber.Ctx, err error)`, since `c` is a pointer to an interface, we need to dereference `*c` when calling interface methods like `SendStatus`.
Applied to files:
middleware/limiter/limiter_sliding.go
📚 Learning: 2024-06-30T00:38:06.580Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3051
File: middleware/session/session.go:215-216
Timestamp: 2024-06-30T00:38:06.580Z
Learning: Parallel tests for `Session.Save` already exist in the `middleware/session/session_test.go` file, specifically in the `Test_Session_Save` and `Test_Session_Save_Expiration` functions.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2025-10-16T07:19:52.418Z
Learnt from: grivera64
Repo: gofiber/fiber PR: 3807
File: adapter_test.go:118-144
Timestamp: 2025-10-16T07:19:52.418Z
Learning: In the Fiber codebase, the linter does not allow `require` assertions from within HTTP handlers (including net/http-style handlers). Use `t.Fatalf`, `t.Errorf`, or similar `testing.T` methods for error handling inside handler functions instead.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2025-05-07T13:07:33.899Z
Learnt from: mdelapenya
Repo: gofiber/fiber PR: 3434
File: docs/api/services.md:39-43
Timestamp: 2025-05-07T13:07:33.899Z
Learning: When documenting Go interface methods in the Fiber project, avoid showing method signatures with the interface type as the receiver (e.g., `func (d *Service) Method()`) since interfaces cannot be used as receivers in Go. Instead, show just the method signature without a receiver or use a placeholder implementation name.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2024-12-13T08:14:22.851Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: hooks_test.go:228-228
Timestamp: 2024-12-13T08:14:22.851Z
Learning: In Go test files, prefer using the `require` methods from the `testify` package for assertions instead of manual comparisons and calls to `t.Fatal` or `t.Fatalf`.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2024-11-15T07:56:21.623Z
Learnt from: ReneWerner87
Repo: gofiber/fiber PR: 3161
File: app.go:923-932
Timestamp: 2024-11-15T07:56:21.623Z
Learning: In the Fiber framework, breaking changes are acceptable when moving from version 2 to version 3, including modifications to method signatures such as in the `Test` method in `app.go`.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 2922
File: middleware/cors/utils.go:63-71
Timestamp: 2024-10-08T19:06:06.583Z
Learning: The project uses the testify/assert package for assertions in unit tests.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2024-10-02T23:03:31.727Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/store.go:164-167
Timestamp: 2024-10-02T23:03:31.727Z
Learning: Unit tests in this project use testify require.
Applied to files:
middleware/limiter/limiter_test.go
📚 Learning: 2024-10-12T10:01:44.206Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/middleware_test.go:190-191
Timestamp: 2024-10-12T10:01:44.206Z
Learning: When testing session `IdleTimeout` expiration, it's acceptable to use `time.Sleep` to simulate the passage of time in tests.
Applied to files:
middleware/limiter/limiter_test.go
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: unit (1.25.x, macos-latest)
- GitHub Check: repeated
- GitHub Check: unit (1.25.x, windows-latest)
- GitHub Check: lint
- GitHub Check: Compare
🔇 Additional comments (6)
middleware/limiter/limiter_test.go (2)
375-399: Nice focused regression test for disabled headers + skip path
TestLimiterSlidingSkipsPostUpdateWhenHeadersDisabledcleanly verifies that withDisableHeaders: trueand no skip flags, the sliding limiter only does one get/set on storage. This directly guards against unnecessary post-handler storage work and matches the newif skipHit || !cfg.DisableHeaderscondition.
798-827: Window rotation / expiration tests align well with sliding-window mathBoth
Test_Limiter_Sliding_Window_RecalculatesAfterHandlerDelayandTest_Limiter_Sliding_Window_ExpiresStalePrevHitsdo a good job of:
- Exercising recomputation of rate/remaining after handler-induced delays, ensuring headers are based on the rotated window.
- Verifying that
prevHitsare dropped after more than one full window, avoiding overly strict limiting.The sleep margins look sufficient for second‑granularity timestamps, so these should be stable under normal CI load.
Also applies to: 829-853
middleware/limiter/limiter_sliding.go (4)
64-79: Core sliding-window rate/remaining computation now correctly uses dynamicmaxRequestsThe refactor to:
- Rotate the window via
rotateWindow(e, ts, expiration),- Compute
rate := int(float64(e.prevHits)*weight) + e.currHitswithweightderived fromresetInSec/expiration, and- Derive
remaining := maxRequests - rateusing the per-requestmaxRequestsfromMaxFunc,ensures dynamic limits are consistently applied for both enforcement and header values. Persisting with
ttlDuration(resetInSec, expiration)also matches the “end of next window” TTL semantics described in the comment.This looks logically sound and aligns with the new tests around dynamic
MaxFuncand header behavior.Also applies to: 92-96, 100-111
119-157: Skip‑hit path and window rotation fix the previous race and bucket mis-accountingThe combination of:
- Recomputing
ts,resetInSec,weightand re-runningrotateWindowafterc.Next(),- Using
bucketForOriginalHit(e, windowExpiresAt, ts, expiration)to decide whether to decrementcurrHitsorprevHits(or neither) for a skipped request, and- Recalculating
rate/remainingbefore persisting and updating headers,addresses the earlier issues where:
- A long-running request could have its hit moved into the previous window, but the decrement still targeted
currHits.- The remaining calculation after skip was previously adjusted by
+1instead of recomputing from the sliding-window formula.The updated logic is guarded by
if skipHit || !cfg.DisableHeaders, so you avoid extra storage churn when neither skipping nor emitting headers. Overall, this is a solid, race-resistant implementation of the skip behavior.Also applies to: 163-197
199-225: TTL helpers correctly guard against duration overflow
ttlDurationandsecondsToDurationprovide a clear and safe way to derive:
- A reset duration from
resetInSec,- The base expiration window, and
- A combined TTL clamped to
math.MaxInt64when either seconds are too large orresetDuration + expirationDurationwould overflow.This is a nice improvement over ad‑hoc arithmetic and should make behavior robust even with very large expiration values.
24-28: Config validation prevents the division-by-zero risk; however, the current approach lacks clarity about sub-second expiration handlingThe concern in the review is partially mitigated by existing code. When
cfg.Expirationis set to a sub-second value like200 * time.Millisecond, the config validation inconfig.go(line 111) detects this becauseint(0.2) <= 0evaluates to true, and automatically replaces it with the default1 * time.Minute. This prevents theexpiration == 0scenario that would cause division by zero at line 72.However, the review's underlying point remains valid: the behavior is implicit rather than explicit. The current code silently converts sub-second values without documentation, and the truncation via
uint64(cfg.Expiration.Seconds())is still a design issue even though it's protected by validation. Consider adding explicit validation with clear error messaging or documentation to make the sub-second limitation intentional and obvious to users.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request effectively addresses the issue of the sliding window limiter not respecting the dynamic MaxFunc value. The changes correctly use the dynamic maxRequests for rate calculation and update the logic for skipped requests to re-compute allowance and refresh headers, which is a solid improvement. The refactoring of window rotation and TTL calculation logic into separate functions enhances code clarity and robustness, especially with the added overflow checks. The new tests, including the regression test for MaxFunc override and scenarios with handler delays, are comprehensive and ensure the correctness of the fixes. I've pointed out a minor correctness issue regarding floating-point truncation in the rate calculation which could make the limiter more permissive than intended. Overall, this is a high-quality contribution.
There was a problem hiding this comment.
Actionable comments posted: 0
🧹 Nitpick comments (1)
middleware/limiter/limiter_sliding.go (1)
199-215: TTL helpers robustly guard againsttime.Durationoverflow; consider a tiny constant for readability
ttlDuration+secondsToDurationcorrectly:
- Convert seconds to
time.Durationonly whenseconds <= math.MaxInt64 / int64(time.Second).- Clamp to
time.Duration(math.MaxInt64)if either conversion would overflow or ifresetDuration + expirationDurationwould overflow.This is a solid defensive improvement over ad-hoc TTL math. If you want a minor readability tweak, you could factor
time.Duration(math.MaxInt64)into a small unexportedconst/var (e.g.,maxDuration) to avoid repeating the literal and make the intent clearer, but that’s purely cosmetic.Also applies to: 217-225
📜 Review details
Configuration used: CodeRabbit UI
Review profile: CHILL
Plan: Pro
📒 Files selected for processing (1)
middleware/limiter/limiter_sliding.go(5 hunks)
🧰 Additional context used
🧠 Learnings (1)
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3016
File: middleware/session/config.go:122-122
Timestamp: 2024-10-08T19:06:06.583Z
Learning: In `DefaultErrorHandler(c *fiber.Ctx, err error)`, since `c` is a pointer to an interface, we need to dereference `*c` when calling interface methods like `SendStatus`.
Applied to files:
middleware/limiter/limiter_sliding.go
🧬 Code graph analysis (1)
middleware/limiter/limiter_sliding.go (2)
log/fiberlog.go (1)
Errorf(49-51)constants.go (1)
StatusBadRequest(73-73)
⏰ Context from checks skipped due to timeout of 90000ms. You can increase the timeout in your CodeRabbit configuration to a maximum of 15 minutes (900000ms). (5)
- GitHub Check: unit (1.25.x, windows-latest)
- GitHub Check: unit (1.25.x, macos-latest)
- GitHub Check: lint
- GitHub Check: repeated
- GitHub Check: Compare
🔇 Additional comments (4)
middleware/limiter/limiter_sliding.go (4)
5-8: DynamicmaxRequestsand Ceil-based rate calculation look correct; verifyExpirationcan’t be zeroUsing
maxRequests := cfg.MaxFunc(c)consistently forremaining := maxRequests - rateand settingX-RateLimit-Limitfixes the staticcfg.Maxissue, and switching tomath.Ceil(float64(e.prevHits)*weight)correctly avoids truncating fractional previous-window hits.One assumption here is that
expiration := uint64(cfg.Expiration.Seconds())is always ≥ 1; if a caller configuresExpiration < time.Second,expirationbecomes 0 andweight := float64(resetInSec) / float64(expiration)will panic with divide-by-zero. If this isn’t already enforced inConfigvalidation, consider guarding or documenting the minimum expiration.Also applies to: 64-80, 100-110
119-156: Skip logic + post-handler recomputation correctly model sliding window and dynamic maxThe
skipHitpredicate based onSkipSuccessfulRequests/SkipFailedRequestsplus the guarded blockif skipHit || !cfg.DisableHeadersis a good balance between correctness and overhead: you only re-read state when you need to adjust hits or refresh headers.Recomputing
ts, callingrotateWindowagain, recomputingweight, and then rebuildingrateandremainingensures the second pass uses a fresh view of the window (addressing the stale-weightconcern) and includes concurrent hits that arrived whilec.Next()ran. UsingbucketForOriginalHit(e, windowExpiresAt, ts, expiration)to decide which bucket to decrement avoids the earlier race where a skipped request could accidentally remove another request’s hit after a window rotation.Behaviorally this matches the objectives: dynamic
maxRequestsis honored, skipped hits are rolled back against the correct bucket, and X-RateLimit headers reflect the updated sliding-window state.
163-185:rotateWindowcorrectly handles single and multi-window expirationsThe new
rotateWindowlogic:
- Initializes
e.expon first use.- For
ts >= e.exp, computeselapsed := ts - e.expand:
- Resets both
prevHitsandcurrHitswhenelapsed >= expiration, so hits older than a full window are fully discarded.- Otherwise shifts
currHitsintoprevHits, zeroscurrHits, and setse.exp = ts + expiration - elapsed, givingresetInSec = e.exp - ts = expiration - elapsed.This fixes the earlier bug where
prevHitscould retain hits from windows that should have expired, and it keepsresetInSecconsistent with the sliding-window math used forweight.
187-197:bucketForOriginalHitcleanly identifies the correct bucket for skipped hitsUsing the captured
windowExpiresAt(requestExpiration) to decide where the original hit landed works well:
ts < requestExpiration→ still in the window that was “current” when the hit was added ⇒ decrementcurrHits.ts-requestExpiration < expiration→ now in the immediately previous window ⇒ decrementprevHits.- Otherwise, the hit’s window is older than one full
expirationand is no longer counted ⇒ returnnil.The boundary choices (
<vs<=) are appropriate: atts == requestExpirationthe hit moves intoprevHits, and oncets-requestExpiration >= expirationthe hit falls completely out of the sliding window. This gives the skip logic the necessary precision without over-complicating the state.
|
/gemini review |
There was a problem hiding this comment.
Code Review
This pull request provides a comprehensive fix for several issues in the sliding window limiter. It correctly implements support for dynamic request limits using MaxFunc, which was a critical bug. The logic for window rotation, calculating request rates, and handling skipped requests has been significantly improved for correctness, especially in scenarios involving long-running requests or idle periods. Additionally, the introduction of overflow checks for TTL calculation makes the middleware more robust. The new tests are thorough and cover the fixed bugs and edge cases well. Overall, this is an excellent set of changes that greatly improves the reliability of the sliding window limiter.
|
@codex review |
|
Codex Review: Didn't find any major issues. Another round soon, please! ℹ️ About Codex in GitHubYour team has set up Codex to review pull requests in this repo. Reviews are triggered when you
If Codex has suggestions, it will comment; otherwise it will react with 👍. Codex can also answer questions or update the PR. Try commenting "@codex address that feedback". |
|
@gaby Can you mark the place in the PR where the problem was, which is why it was not reset? |
|
👍 Excellent improvement! Are you planning to enhance it further, as mentioned in #3749 ? |
Summary
Fixes #3867