Skip to content

🐛 bug: Fixes and improvements for limiter middleware#3899

Merged
ReneWerner87 merged 4 commits intov2from
update-slidingwindow.new-for-expiration-reset
Nov 26, 2025
Merged

🐛 bug: Fixes and improvements for limiter middleware#3899
ReneWerner87 merged 4 commits intov2from
update-slidingwindow.new-for-expiration-reset

Conversation

@gaby
Copy link
Member

@gaby gaby commented Nov 25, 2025

Summary

  • This pull request significantly improves the robustness of the rate limiting middleware by addressing edge cases related to skipped requests and limiter expiration.

Fixes #3892

@gaby gaby requested a review from a team as a code owner November 25, 2025 04:18
@coderabbitai
Copy link
Contributor

coderabbitai bot commented Nov 25, 2025

Note

Other AI code review bot(s) detected

CodeRabbit 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.

Walkthrough

Normalize expiration checks to use duration directly and ceil-rounded seconds (minimum 1s); refactor fixed and sliding limiter logic to use timestamp-based windows, recompute expirations/TTLs and hit migrations, add timestamp updater, and add extensive concurrency- and TTL-aware tests. Minor formatting tweaks in idempotency files.

Changes

Cohort / File(s) Summary
Configuration
middleware/limiter/config.go
Use direct duration comparison (cfg.Expiration <= 0) instead of converting to seconds and casting to int for expiration validation.
Fixed Window
middleware/limiter/limiter_fixed.go
Compute expiration as ceil(seconds) with minimum 1s; store TTLs as time.Duration(expiration) * time.Second; switch to timestamp-based window/reset logic, recompute resetInSec/remaining, normalize hit counts, unify skip-success/skip-failure paths, and invoke StartTimeStampUpdater.
Sliding Window
middleware/limiter/limiter_sliding.go
Apply ceil-rounded expiration (min 1s); implement timestamp-based expiry handling with branches (reset, migrate curr→prev, adjust exp), replace simple decrements with multi-branch state transitions, recalc resetInSec/weight/rate/remaining, and persist with TTL = resetInSec + expiration; mirror logic in post-c.Next skip paths.
Tests & Test Helpers
middleware/limiter/limiter_test.go
Add recordingStorage (mutex-protected in-memory store with TTL tracking), atomic timestamp controls for sub-second simulation, and numerous tests covering fixed/sliding windows, sub-second expirations, skip-long-request and TTL-preservation scenarios.
Idempotency formatting
middleware/idempotency/response_msgp.go, middleware/idempotency/response_msgp_test.go
Minor blank-line/variable reordering and formatting adjustments only; no behavioral changes.
Module manifest
go.mod
Module file present/updated in diff.

Sequence Diagram(s)

sequenceDiagram
    participant Client
    participant Limiter
    participant Timer
    participant Storage

    Client->>Limiter: Incoming request
    Limiter->>Timer: Read shared timestamp
    Limiter->>Storage: Get entry
    alt Missing entry or window expired
        Limiter->>Limiter: Initialize window (expiration = ceil(sec) ≥ 1s)
        Limiter->>Limiter: Reset hit counters, compute resetInSec, remaining
    else Entry exists within window
        Limiter->>Limiter: Compute elapsed, migrate/reset/move curr→prev as needed
        Limiter->>Limiter: Recalculate weight, rate, resetInSec, remaining
    end
    Limiter->>Storage: Persist entry with TTL = resetInSec + expiration
    alt Limit exceeded
        Limiter->>Client: Respond 429
    else Within limit
        Limiter->>Client: c.Next (allow)
        Client->>Limiter: Response outcome (success/fail)
        Limiter->>Limiter: Apply skip-success/skip-failure logic (same timestamp-based transitions)
        Limiter->>Storage: Persist updated entry with adjusted TTL
    end
Loading

Estimated code review effort

🎯 4 (Complex) | ⏱️ ~45–90 minutes

  • Review timestamp math, ceil/TTL conversions, and enforcement of the 1s minimum.
  • Verify parity between main request-path logic and post-c.Next skip-success/skip-failure branches in both fixed and sliding implementations.
  • Inspect new tests and recordingStorage for timing sensitivity and potential flakiness.
  • Check StartTimeStampUpdater lifecycle and interactions with storage TTLs.

Possibly related PRs

Suggested reviewers

  • sixcolors
  • efectn
  • ReneWerner87

Poem

🐰 Hops on ceilings, seconds rounded true,

Timestamps ticking, windows born anew.
Counters shuffle, TTLs snug and small,
Limits breathe out — then welcome traffic's call.
A rabbit cheers: let requests leap and fall!

Pre-merge checks and finishing touches

❌ Failed checks (2 warnings, 1 inconclusive)
Check name Status Explanation Resolution
Description check ⚠️ Warning The PR description is minimal but addresses the core issue. However, it does not follow the template structure with required sections like 'Changes introduced', 'Type of change', and 'Checklist' items. Expand the description to include the required template sections: Changes introduced (with checkboxes), Type of change selection, and Checklist verification items to meet repository standards.
Docstring Coverage ⚠️ Warning Docstring coverage is 68.42% which is insufficient. The required threshold is 80.00%. You can run @coderabbitai generate docstrings to improve docstring coverage.
Out of Scope Changes check ❓ Inconclusive Minor formatting changes in middleware/idempotency/response_msgp.go and response_msgp_test.go are out of scope but cosmetic; core changes focus on limiter expiration fixes and tests as required. Clarify if the idempotency middleware formatting changes (response_msgp.go, response_msgp_test.go) are intentional; consider separating cosmetic formatting into a separate commit or PR for clarity.
✅ Passed checks (2 passed)
Check name Status Explanation
Title check ✅ Passed The title '🐛 bug: Fixes and improvements for limiter middleware' directly addresses the main changes by indicating bug fixes for the limiter middleware, which aligns with the PR's focus on improving rate limiting robustness.
Linked Issues check ✅ Passed The PR successfully addresses the linked issue #3892 by implementing expiration reset logic in both fixed and sliding window implementations, adding comprehensive tests, and fixing edge cases with skipped requests.
✨ Finishing touches
  • 📝 Generate docstrings
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Post copyable unit tests in a comment
  • Commit unit tests in branch update-slidingwindow.new-for-expiration-reset

📜 Recent review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between ef2f9a5 and 7117b17.

📒 Files selected for processing (1)
  • middleware/limiter/limiter_test.go (7 hunks)
🧰 Additional context used
🧠 Learnings (7)
📚 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: 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
📚 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: 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-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-29T12:37:27.581Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.

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). (11)
  • GitHub Check: Build (1.22.x, macos-latest)
  • GitHub Check: Build (1.21.x, macos-latest)
  • GitHub Check: Build (1.23.x, macos-latest)
  • GitHub Check: Build (1.21.x, windows-latest)
  • GitHub Check: Build (1.20.x, macos-latest)
  • GitHub Check: Build (1.20.x, windows-latest)
  • GitHub Check: Build (1.18.x, windows-latest)
  • GitHub Check: Build (1.20.x, ubuntu-latest)
  • GitHub Check: Build (1.17.x, windows-latest)
  • GitHub Check: Build (1.19.x, windows-latest)
  • GitHub Check: Compare
🔇 Additional comments (4)
middleware/limiter/limiter_test.go (4)

18-71: LGTM! Well-designed test helper storage.

The recordingStorage implementation is thread-safe and correctly captures TTL values for test verification. Good practices include:

  • Mutex protection for all shared state access
  • Defensive copying in Get and Set to prevent data races
  • Clean separation of concerns for test infrastructure

324-351: LGTM! Test correctly verifies expiration reset behavior.

This test directly addresses the PR objective (issue #3892) by confirming that the sliding window limiter properly resets after the expiration window elapses, allowing subsequent requests to succeed.


547-581: LGTM! Edge case coverage for long-running skipped requests.

Both tests properly verify that when a skipped request's execution exceeds the expiration window, the limiter still maintains correct state. This addresses the PR objective about handling "skipped or long-running requests without leaving buckets permanently exceeded."

Also applies to: 664-698


930-963: LGTM! Comprehensive sub-second expiration test coverage.

These tests effectively verify that sub-second expirations are normalized to at least 1 second with proper header values, consistent TTLs, and correct window persistence. The use of atomic timestamp manipulation allows precise testing of time-based behavior without flaky time.Sleep calls.

Good practices:

  • Tests intentionally omit t.Parallel() due to global timestamp manipulation (safe pattern)
  • Proper cleanup with defer to restore original timestamp
  • Covers both fixed and sliding window implementations

Note: Test_Limiter_Fixed_Window_FractionalExpiration_TTLSynced incorrectly calls t.Parallel() - see separate comment.

Also applies to: 996-1032, 1034-1067

Tip

📝 Customizable high-level summaries are now available in beta!

You can now customize how CodeRabbit generates the high-level summary in your pull requests — including its content, structure, tone, and formatting.

  • Provide your own instructions using the high_level_summary_instructions setting.
  • Format the summary however you like (bullet lists, tables, multi-section layouts, contributor stats, etc.).
  • Use high_level_summary_in_walkthrough to move the summary from the description to the walkthrough section.

Example instruction:

"Divide the high-level summary into five sections:

  1. 📝 Description — Summarize the main change in 50–60 words, explaining what was done.
  2. 📓 References — List relevant issues, discussions, documentation, or related PRs.
  3. 📦 Dependencies & Requirements — Mention any new/updated dependencies, environment variable changes, or configuration updates.
  4. 📊 Contributor Summary — Include a Markdown table showing contributions:
    | Contributor | Lines Added | Lines Removed | Files Changed |
  5. ✔️ Additional Notes — Add any extra reviewer context.
    Keep each section concise (under 200 words) and use bullet or numbered lists for clarity."

Note: This feature is currently in beta for Pro-tier users, and pricing will be announced later.


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.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@gemini-code-assist
Copy link
Contributor

Summary of Changes

Hello @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 significantly improves the robustness of the rate limiting middleware by addressing edge cases related to skipped requests and limiter expiration. The changes ensure that rate limiters maintain accurate state, prevent erroneous negative counts, and correctly reset their windows, particularly when requests are processed over extended periods or when using sub-second expiration configurations. The added tests provide strong validation for these critical behavioral corrections.

Highlights

  • Rate Limiter Logic Refinement: The pull request refines the logic for both fixed and sliding window rate limiters, specifically addressing how 'skipped' requests are handled to prevent negative hit counts and ensure accurate rate calculations after a limiter's expiration.
  • Expiration Handling for Skipped Requests: Updates were made to refresh skip branches in both limiter types before decrementing, ensuring that expired buckets reset correctly instead of allowing hit counts to go negative.
  • Accurate Rate and Remaining Value Recomputation: The system now recomputes rate and remaining values after skipped requests, ensuring that decrements target the correct time window and reflect the current state of the limiter.
  • Enhanced Test Coverage: New regression tests have been added to validate the behavior of long-running skipped requests, confirming that windows reset as expected and rate limits are applied correctly even under these conditions. This includes tests for sub-second and fractional expirations.
Using Gemini Code Assist

The 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 /gemini <command> or @gemini-code-assist <command>. Below is a summary of the supported commands on the current page.

Feature Command Description
Code Review /gemini review Performs a code review for the current pull request in its current state.
Pull Request Summary /gemini summary Provides a summary of the current pull request in its current state.
Comment @gemini-code-assist Responds in comments when explicitly tagged, both in pull request comments and review comments.
Help /gemini help Displays a list of available commands.

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 .gemini/ folder in the base of the repository. Detailed instructions can be found here.

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

  1. Review the Privacy Notices, Generative AI Prohibited Use Policy, Terms of Service, and learn how to configure Gemini Code Assist in GitHub here. Gemini can make mistakes, so double check it and use code with caution.

@gaby gaby changed the title Guard skip decrements after limiter expiration 🐛 bug: Fixes and improvements for limiter middleware Nov 25, 2025
Copy link
Contributor

@gemini-code-assist gemini-code-assist bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Code Review

This pull request effectively addresses a critical bug where rate limiter counts could become incorrect for skipped requests, especially when those requests are long-running and outlive the limiter's expiration window. The changes in both the fixed and sliding window implementations are robust, and the addition of regression tests is excellent. I've included a few suggestions to enhance maintainability by reducing some code duplication and pointed out a potential issue in one of the new tests.

Comment on lines +780 to +782
if secondTTL < firstTTL {
t.Fatalf("expected skip branch to preserve TTL, got first=%s second=%s", firstTTL, secondTTL)
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

medium

This test assertion appears to be incorrect. For the sliding window limiter, the TTL passed to the storage is calculated as time.Duration(resetInSec+expiration)*time.Second, where resetInSec = e.exp - ts.

When a request is processed, there are two calls to storage.Set: one before c.Next() (for incrementing) and one after (for decrementing, in the case of skipped requests). The timestamp ts will be greater for the second call. This means resetInSec will be smaller, and therefore secondTTL will naturally be smaller than firstTTL.

The current assertion if secondTTL < firstTTL will cause the test to fail, but the failure message "expected skip branch to preserve TTL" suggests you expect them to be equal or for secondTTL to be greater.

The implementation's TTL calculation seems correct for ensuring the storage entry expires at a fixed absolute time, so the decreasing TTL duration is expected. This test might need to be adjusted to reflect this expected behavior, for instance, by asserting that secondTTL is less than firstTTL but within an expected range.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (3)
middleware/limiter/limiter_test.go (3)

69-71: Rename unused receiver to _.

Static analysis flagged the unused receiver. Since Close doesn't need to access the storage state, rename the receiver.

-func (s *recordingStorage) Close() error {
+func (*recordingStorage) Close() error {
 	return nil
 }

935-939: Fix variable naming to follow Go conventions.

Static analysis flagged baseTs should be baseTS per Go's style guide for initialisms.

-	baseTs := uint32(time.Now().Unix())
+	baseTS := uint32(time.Now().Unix())
 	refreshTimestamp := func(ts uint32) {
 		atomic.StoreUint32(&utils.Timestamp, ts)
 	}
-	refreshTimestamp(baseTs)
+	refreshTimestamp(baseTS)

Apply the same change at lines 951 and 958 where baseTs is referenced.


1033-1037: Same naming fix needed here.

Apply the same baseTsbaseTS rename as noted for the fixed window test.

-	baseTs := uint32(time.Now().Unix())
+	baseTS := uint32(time.Now().Unix())
 	refreshTimestamp := func(ts uint32) {
 		atomic.StoreUint32(&utils.Timestamp, ts)
 	}
-	refreshTimestamp(baseTs)
+	refreshTimestamp(baseTS)

Also update references at lines 1049 and 1056.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 0786b39 and d2fcc26.

📒 Files selected for processing (4)
  • middleware/limiter/config.go (1 hunks)
  • middleware/limiter/limiter_fixed.go (4 hunks)
  • middleware/limiter/limiter_sliding.go (4 hunks)
  • middleware/limiter/limiter_test.go (7 hunks)
🧰 Additional context used
🧠 Learnings (7)
📚 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: 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-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-29T12:37:27.581Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.

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
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3051
File: middleware/session/session.go:215-216
Timestamp: 2024-10-08T19:06:06.583Z
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
🧬 Code graph analysis (3)
middleware/limiter/limiter_test.go (1)
utils/time.go (1)
  • Timestamp (13-13)
middleware/limiter/limiter_fixed.go (1)
utils/time.go (1)
  • Timestamp (13-13)
middleware/limiter/limiter_sliding.go (1)
utils/time.go (1)
  • Timestamp (13-13)
🪛 GitHub Check: lint
middleware/limiter/limiter_test.go

[warning] 69-69:
unused-receiver: method receiver 's' is not referenced in method's body, consider removing or renaming it as _ (revive)


[failure] 1033-1033:
ST1003: var baseTs should be baseTS (stylecheck)


[failure] 935-935:
ST1003: var baseTs should be baseTS (stylecheck)

⏰ 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). (10)
  • GitHub Check: Build (1.23.x, macos-latest)
  • GitHub Check: Build (1.23.x, windows-latest)
  • GitHub Check: Build (1.22.x, macos-latest)
  • GitHub Check: Build (1.19.x, windows-latest)
  • GitHub Check: Build (1.20.x, windows-latest)
  • GitHub Check: Build (1.21.x, windows-latest)
  • GitHub Check: Build (1.20.x, macos-latest)
  • GitHub Check: Build (1.18.x, macos-latest)
  • GitHub Check: Build (1.17.x, macos-latest)
  • GitHub Check: Compare
🔇 Additional comments (14)
middleware/limiter/config.go (1)

115-117: LGTM!

The simplified check cfg.Expiration <= 0 correctly validates non-positive durations. This aligns well with the new ceiling-based expiration model in the limiter implementations, which will round sub-second expirations up to at least 1 second.

middleware/limiter/limiter_fixed.go (3)

22-27: LGTM!

The ceiling-based expiration calculation with minimum 1-second enforcement is a sound approach. This ensures sub-second configurations don't result in immediate expiration and provides a defensive safeguard.


73-73: LGTM!

Using the computed expiration value for storage TTL ensures consistency with the actual window duration.


98-115: Core fix correctly implemented.

The skip branch now properly refreshes the timestamp and recomputes window state before decrementing. The guard if e.currHits > 0 prevents negative hit counts after window expiration during long-running requests. This addresses the PR objective.

middleware/limiter/limiter_sliding.go (3)

22-27: LGTM!

Consistent ceiling-based expiration calculation with the fixed window implementation.


57-72: LGTM!

The window transition logic correctly handles both full expiration (resetting both counters) and partial expiration (migrating current to previous). This ensures the sliding window algorithm maintains accurate state.


127-158: Core sliding window fix correctly implemented.

The movedToPrev flag correctly tracks whether the window rolled during request processing, ensuring the decrement targets the appropriate counter (prevHits vs currHits). The guards prevent negative counters in all edge cases.

middleware/limiter/limiter_test.go (7)

547-581: Good regression test for the core fix.

This test correctly validates that the fixed window limiter handles long-running requests that span expiration boundaries. The sequence (successful skipped → failed counted → rate limited) confirms the expiration reset behavior works as expected.


664-698: Good regression test for sliding window.

Mirrors the fixed window test for the sliding window implementation, ensuring consistent behavior across both limiter types.


741-783: TTL preservation test is valuable.

This test verifies that the skip branch doesn't inadvertently shorten the storage TTL, which would cause premature garbage collection of rate limit state.


324-351: LGTM!

Clean test for basic sliding window reset behavior after expiration.


965-994: LGTM!

Good test verifying that both Set calls (initial and skip branch) use consistent TTL values.


996-1026: LGTM!

Validates that the ceiling-rounded 1500ms expiration (→ 2 seconds) persists correctly across window boundaries.


18-48: LGTM!

The recordingStorage implementation is well-structured for test purposes with proper mutex protection and defensive copying. Based on learnings, the project uses testify for assertions, but the custom storage helper is appropriate here for capturing TTL values.

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

♻️ Duplicate comments (1)
middleware/limiter/limiter_test.go (1)

741-783: TTL expectation in Sliding_Window_Skip_TTL_Preservation is likely inverted

The logic in Test_Limiter_Sliding_Window_Skip_TTL_Preservation currently fails the test when secondTTL < firstTTL and the failure message claims “expected skip branch to preserve TTL”. For the current SlidingWindow implementation, the TTL passed to storage is derived from the remaining window (resetInSec) plus the expiration, so on the second Set call (after c.Next()), the effective timestamp is later and the TTL should naturally be smaller, not larger.

Unless you’ve intentionally changed the TTL semantics to keep it constant across these two Set calls, this assertion is backwards and will fail under correct behavior.

Consider inverting the condition to assert that the TTL decreases:

-	if secondTTL < firstTTL {
-		t.Fatalf("expected skip branch to preserve TTL, got first=%s second=%s", firstTTL, secondTTL)
-	}
+	if secondTTL >= firstTTL {
+		t.Fatalf("expected subsequent TTL to shrink as time advances, got first=%s second=%s", firstTTL, secondTTL)
+	}

This keeps the useful check that the initial TTL exceeds the configured expiration while aligning the comparison with the time-based TTL calculation.

🧹 Nitpick comments (1)
middleware/limiter/limiter_test.go (1)

18-71: recordingStorage helper looks good; adjust Close receiver to satisfy linter

The in-test recordingStorage implementation is concurrency-safe and fits the storage interface well (byte copies, mutex protection, TTL recording). The only issue is the linter warning about the unused receiver in Close.

You can silence this without changing behavior by dropping the receiver name:

-func (s *recordingStorage) Close() error {
+func (*recordingStorage) Close() error {
 	return nil
}

This keeps the method on *recordingStorage for interface compatibility while resolving the unused-receiver warning.

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between d2fcc26 and 46f58a9.

📒 Files selected for processing (1)
  • middleware/limiter/limiter_test.go (7 hunks)
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3051
File: middleware/session/session.go:215-216
Timestamp: 2024-10-08T19:06:06.583Z
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: 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
📚 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: 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-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-29T12:37:27.581Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.

Applied to files:

  • middleware/limiter/limiter_test.go
🧬 Code graph analysis (1)
middleware/limiter/limiter_test.go (1)
utils/time.go (1)
  • Timestamp (13-13)
🪛 GitHub Check: lint
middleware/limiter/limiter_test.go

[warning] 69-69:
unused-receiver: method receiver 's' is not referenced in method's body, consider removing or renaming it as _ (revive)


[failure] 931-931:
File is not gofmt-ed with -s (gofmt)

⏰ 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). (13)
  • GitHub Check: Build (1.22.x, macos-latest)
  • GitHub Check: Build (1.23.x, windows-latest)
  • GitHub Check: Build (1.21.x, macos-latest)
  • GitHub Check: Build (1.23.x, ubuntu-latest)
  • GitHub Check: Build (1.22.x, windows-latest)
  • GitHub Check: Build (1.21.x, ubuntu-latest)
  • GitHub Check: Build (1.20.x, macos-latest)
  • GitHub Check: Build (1.20.x, windows-latest)
  • GitHub Check: Build (1.19.x, macos-latest)
  • GitHub Check: Build (1.18.x, ubuntu-latest)
  • GitHub Check: Build (1.17.x, ubuntu-latest)
  • GitHub Check: Build (1.17.x, windows-latest)
  • GitHub Check: Compare
🔇 Additional comments (2)
middleware/limiter/limiter_test.go (2)

324-351: Sliding-window reset-after-expiration test matches the reported bug scenario

Test_Limiter_Sliding_Window_Reset_After_Expiration (Max=1, 1s expiration, then a ~3s sleep) correctly exercises the bug from #3892: limiter hits 429 on the second immediate request, then recovers to 200 after the window has clearly expired. The flow and expectations look correct and give good regression coverage.


547-581: Long-running request + SkipSuccessfulRequests tests exercise critical edge case well

Both Test_Limiter_Fixed_Window_Skip_Long_Request_Expiration and Test_Limiter_Sliding_Window_Skip_Long_Request_Expiration model:

  • A first request that succeeds but runs longer than the expiration window.
  • Subsequent failing requests that should be counted and eventually limited.
  • Use of app.Test(..., -1) plus time.Sleep to allow the long request to complete.

The expectations (200 → 500 → 429) align with the intended semantics of SkipSuccessfulRequests and ensure long-running successes don’t leave the limiter in a permanently exceeded state. This is a solid regression harness for the described bug.

Also applies to: 664-698

Copy link
Contributor

@coderabbitai coderabbitai bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 0

🧹 Nitpick comments (1)
middleware/limiter/limiter_test.go (1)

741-783: TTL recording test is precise; be aware it tightly couples to internal TTL semantics

Using recordingStorage and capturing expirations under the mutex is a solid way to assert on sliding-window TTL behavior when skipping successful requests. The checks:

  • At least two Set calls recorded.
  • Initial TTL strictly greater than Expiration.
  • Second TTL not less than the first.

encode fairly specific expectations about how the skip branch updates TTLs. That’s fine as a regression test, but keep in mind it will need adjustment if future internal TTL calculations change (even if external behavior stays equivalent).

📜 Review details

Configuration used: CodeRabbit UI

Review profile: CHILL

Plan: Pro

📥 Commits

Reviewing files that changed from the base of the PR and between 46f58a9 and ef2f9a5.

📒 Files selected for processing (3)
  • middleware/idempotency/response_msgp.go (2 hunks)
  • middleware/idempotency/response_msgp_test.go (1 hunks)
  • middleware/limiter/limiter_test.go (7 hunks)
✅ Files skipped from review due to trivial changes (1)
  • middleware/idempotency/response_msgp.go
🧰 Additional context used
🧠 Learnings (7)
📚 Learning: 2024-10-08T19:06:06.583Z
Learnt from: sixcolors
Repo: gofiber/fiber PR: 3051
File: middleware/session/session.go:215-216
Timestamp: 2024-10-08T19:06:06.583Z
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: 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
📚 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: 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-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-29T12:37:27.581Z
Learnt from: efectn
Repo: gofiber/fiber PR: 3162
File: app_test.go:893-895
Timestamp: 2024-11-29T12:37:27.581Z
Learning: In the `Test_App_ShutdownWithContext` function in `app_test.go`, the `clientDone` channel is used to synchronize the client's request completion before proceeding, eliminating the need for additional `time.Sleep` calls.

Applied to files:

  • middleware/limiter/limiter_test.go
🪛 GitHub Check: lint
middleware/limiter/limiter_test.go

[warning] 69-69:
unused-receiver: method receiver 's' is not referenced in method's body, consider removing or renaming it as _ (revive)

⏰ 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). (9)
  • GitHub Check: Build (1.23.x, macos-latest)
  • GitHub Check: Build (1.23.x, windows-latest)
  • GitHub Check: Build (1.19.x, macos-latest)
  • GitHub Check: Build (1.21.x, windows-latest)
  • GitHub Check: Build (1.20.x, windows-latest)
  • GitHub Check: Build (1.21.x, ubuntu-latest)
  • GitHub Check: Build (1.17.x, windows-latest)
  • GitHub Check: Build (1.17.x, macos-latest)
  • GitHub Check: Compare
🔇 Additional comments (9)
middleware/idempotency/response_msgp_test.go (1)

3-4: Generated test formatting change is fine

The added blank line after the package declaration keeps this generated test file gofmt-compliant and doesn’t affect behavior. No further changes needed.

middleware/limiter/limiter_test.go (8)

324-351: Good regression test for sliding window reset after expiration

This test clearly reproduces the “stuck 429” bug for SlidingWindow and verifies that after Expiration elapses (with a generous 3s sleep for a 1s window), the limiter allows requests again. The configuration and expectations match the linked issue’s goal.


547-581: Fixed window long-request + SkipSuccessfulRequests scenario is well covered

This test nicely captures the case of a long-running successful request in a 1s fixed window with SkipSuccessfulRequests=true, then a failing request within the same window, and finally a 429 once the failing request has “consumed” the slot. Using app.Test(..., -1) avoids client timeouts while the handler sleeps.


664-698: Sliding window long-request + SkipSuccessfulRequests parity with fixed window

This is a good sliding-window counterpart to the fixed-window long-request test: same configuration and expectations (200, then 500, then 429), ensuring both implementations agree on how long-running successful requests and subsequent failures interact with the window.


930-963: Fixed window sub-second expiration test: good use of Timestamp without t.Parallel

This test cleanly simulates a 500ms window by pinning utils.Timestamp via atomic.StoreUint32, restoring it with defer, and not calling t.Parallel(), which helps avoid surprises with the global timestamp. It verifies both a successful first request and the presence of X-RateLimit-Reset, then a 429 on the second request at the same logical timestamp, which is exactly what you want for the sub-second normalization behavior.


965-995: Fractional fixed-window TTL sync test is clear and robust

This test uses recordingStorage to assert that for a 500ms fixed window with SkipSuccessfulRequests=true, both Set calls record a TTL of exactly time.Second, confirming that fractional expirations are normalized consistently. The use of t.Parallel() is fine here since no global state is mutated and storage is test-local.


996-1032: Fixed window fractional expiration window-persistence behavior is well specified

The 1500 * time.Millisecond case with manual utils.Timestamp control accurately describes desired behavior:

  • First request at baseTS succeeds.
  • Second request at baseTS+1 is still rate-limited (429), confirming the window persists across the fractional boundary.
  • Third request at baseTS+3 succeeds, confirming reset after the window plus normalization.

Not running this test with t.Parallel() is appropriate given the global timestamp mutation.


1034-1067: Sliding window sub-second expiration mirror test looks correct

This is the sliding-window mirror of the fixed-window sub-second test, with the same utils.Timestamp pinning pattern and 500ms expiration. It validates that:

  • The first request succeeds and sets a non-empty X-RateLimit-Reset.
  • A second request at the same logical timestamp gets 429.

Again, avoiding t.Parallel() here is the right call given the global timestamp manipulation.


18-72: recordingStorage implementation is sound; apply suggested lint fix to Close receiver

The in-memory recordingStorage with mutex protection, defensive copies in Get/Set, and expiration tracking is appropriate for testing TTL behavior under concurrency.

The unused receiver in Close() can be fixed by omitting the receiver name:

-func (s *recordingStorage) Close() error {
+func (*recordingStorage) Close() error {
 	return nil
 }

This satisfies linting rules and clearly signals the receiver isn't used while maintaining interface compliance.

@ReneWerner87 ReneWerner87 merged commit a290d14 into v2 Nov 26, 2025
27 checks passed
@ReneWerner87 ReneWerner87 deleted the update-slidingwindow.new-for-expiration-reset branch November 26, 2025 13:51
@ReneWerner87 ReneWerner87 added this to the v2.52.11 milestone Jan 31, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants