test(e2e): keep rate-limit bursts inside one wall-clock window - #813
Conversation
`ratelimit-e2e` failed on an unrelated PR with `expected undefined to be an instance of APIError` — the second call under RPM=1 succeeded instead of returning 429. The limiter buckets on fixed wall-clock windows: `roll_if_stale` in `crates/aisix-ratelimit/src/window.rs` computes `bucket_start = (now / window_secs) * window_secs`. A burst that straddles a minute boundary therefore lands in two buckets, the later call legitimately gets a fresh allowance, and the assertion flaps purely on when in the minute CI happened to run it. `model-group-member-ratelimit-e2e` already carried a private `awaitWindowHeadroom` for exactly this reason (#1087). Promotes it to the shared harness and applies it to every sibling that bursts against an RPM cap and asserts a 429: ratelimit-e2e, team-member-ratelimit-e2e, passthrough-model-rate-limit-e2e, videos-e2e, ratelimit-cluster-e2e, concurrency-e2e `concurrency-e2e` asks for more headroom (20s): its readiness probes consume the RPM=1 slots themselves, so the probe and the assertion have to land in the same window, and the propagation polling between them takes seconds. Deliberately not guarded: the second `ratelimit-cluster-e2e` case (`backend=memory`, both replicas expected to succeed) — crossing a boundary only loosens the quota there, so it cannot produce a false failure. The helper only sleeps in the last seconds of a minute, so a normal run pays nothing. Its two branches are pinned with fake timers, since the real behaviour is otherwise exercised on almost no run.
|
Warning Review limit reachedYou’ve reached a temporary PR review limit under our Fair Usage Limits Policy. Next review available in: 7 minutes Enable usage-based reviews in Billing to review now. Otherwise, wait until the next included review is available. How can I continue?After more reviews become available, a review can be triggered using the To avoid repeated limits, reduce automatic review volume by pausing incremental auto-reviews earlier, using label-based review opt-in, excluding WIP or generated PR titles, or requesting reviews manually when the PR is ready. If your team needs uninterrupted high-volume reviews, an organization admin can enable usage-based reviews. How do review limits work?CodeRabbit enforces per-developer PR review limits for each organization. Most developers receive the normal plan review availability. For paid Pro and Pro+ PR reviews, CodeRabbit uses adaptive limits for sustained high-volume activity. When a developer's recent PR review activity reaches the 95th percentile or higher among CodeRabbit users, additional reviews become available more gradually as earlier reviews age out of the rolling window. Please refer docs for additional details. Review details⚙️ Run configurationConfiguration used: Organization UI Review profile: CHILL Plan: Pro Run ID: 📒 Files selected for processing (10)
✨ Finishing Touches🧪 Generate unit tests (beta)
Comment |
The failure
ratelimit-e2efailed on an unrelated PR (#809, an Azure streaming-timeout change):The second call under
rpm: 1succeeded instead of returning 429. 153 other tests passed.Why
The limiter buckets on fixed wall-clock windows, not on a sliding window from first use —
roll_if_staleincrates/aisix-ratelimit/src/window.rs:So a burst that straddles a minute boundary lands in two buckets, the later call legitimately gets a fresh allowance, and the assertion flaps purely on when in the minute CI happened to run it. Nothing about the gateway is wrong; the tests just assume a burst is atomic with respect to the window.
Fix
model-group-member-ratelimit-e2ealready carried a privateawaitWindowHeadroomfor exactly this reason (added with #1087). This promotes it to the shared harness and applies it to every sibling that bursts against an RPM cap and asserts a 429:ratelimit-e2eteam-member-ratelimit-e2epassthrough-model-rate-limit-e2evideos-e2eratelimit-cluster-e2e(shared-Redis case)concurrency-e2econcurrency-e2eneeds more because its readiness probes consume the RPM=1 slots themselves — probe and assertion must land in the same window, and the propagation polling between them takes seconds.Deliberately not guarded: the second
ratelimit-cluster-e2ecase (backend=memory, both replicas expected to succeed). Crossing a boundary only loosens the quota there, so it cannot produce a false failure. Guarding it would just add wall-clock time.Cost
The helper returns immediately unless the current minute has less than the requested headroom left, so a normal run pays nothing. Its two branches are pinned with fake timers (
src/harness/window-headroom.test.ts) — the real behaviour only shows up in the last seconds of a minute and would otherwise be exercised on almost no run.Verification
All six guarded suites pass locally (12 tests). The pre-existing
tsc --noEmitwarnings in this tree (apppossibly undefined, 5 of them) are unchanged — verified identical on pristinemain.