-
Notifications
You must be signed in to change notification settings - Fork 11.1k
fix: exclude failed requests from memory rate limit success count #6528
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
base: main
Are you sure you want to change the base?
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -2,9 +2,12 @@ package middleware | |
|
|
||
| import ( | ||
| "context" | ||
| "net/http" | ||
| "net/http/httptest" | ||
| "testing" | ||
| "time" | ||
|
|
||
| "github.com/gin-gonic/gin" | ||
| "github.com/stretchr/testify/assert" | ||
| "github.com/stretchr/testify/require" | ||
| ) | ||
|
|
@@ -32,3 +35,34 @@ func TestModelRedisRateLimitUsesUTCRegardlessOfLocalTimezone(t *testing.T) { | |
| require.NoError(t, err) | ||
| assert.False(t, allowed, "an existing UTC timestamp inside the window must remain limited on a non-UTC host") | ||
| } | ||
|
|
||
| // 成功数限制只统计成功请求,失败请求不占配额;拒绝时返回 JSON 错误体而非空 429。 | ||
| func TestModelMemoryRateLimitSuccessCountIgnoresFailedRequests(t *testing.T) { | ||
| gin.SetMode(gin.TestMode) | ||
|
|
||
| // 限流器是进程级全局且无重置接口,user id 每次唯一才能保证 -count=2 复跑 | ||
| userID := int(time.Now().UnixNano() % 1_000_000_000) | ||
|
Comment on lines
+39
to
+44
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🩺 Stability & Availability | 🟡 Minor | ⚡ Quick win Use deterministic isolation for the global limiter state. A wall-clock-derived ID can collide with another test or repeated execution, reusing stale process-global quota state. Reset/inject the limiter for the test, or allocate IDs from a reserved namespace using an atomic counter. As per coding guidelines, backend tests should prefer deterministic inputs and explicit state. 🤖 Prompt for AI AgentsSource: Coding guidelines |
||
| downstreamStatus := http.StatusOK | ||
| router := gin.New() | ||
| router.GET("/limited", func(c *gin.Context) { | ||
| c.Set("id", userID) | ||
| }, memoryRateLimitHandler(60, 0, 2), func(c *gin.Context) { | ||
| c.Status(downstreamStatus) | ||
| }) | ||
|
|
||
| do := func(status int) *httptest.ResponseRecorder { | ||
| downstreamStatus = status | ||
| return performRateLimitRequest(router, "/limited", "192.0.2.70:12345") | ||
| } | ||
|
|
||
| for range 5 { | ||
| assert.Equal(t, http.StatusInternalServerError, do(http.StatusInternalServerError).Code, "failed requests must not consume the success quota") | ||
| } | ||
|
|
||
| require.Equal(t, http.StatusOK, do(http.StatusOK).Code) | ||
| require.Equal(t, http.StatusOK, do(http.StatusOK).Code) | ||
|
|
||
| limited := do(http.StatusOK) | ||
| require.Equal(t, http.StatusTooManyRequests, limited.Code) | ||
| assert.Contains(t, limited.Body.String(), "您已达到请求数限制", "429 must carry the same error message as the Redis path") | ||
| } | ||
|
Comment on lines
+65
to
+68
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. 🎯 Functional Correctness | 🟡 Minor | ⚡ Quick win Assert the JSON response contract, not only the message text.
🤖 Prompt for AI Agents |
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
🩺 Stability & Availability | 🟠 Major | 🏗️ Heavy lift
Make success-limit admission atomic with success accounting.
CanRequestreleases the limiter lock beforec.Next(), so concurrent requests can all pass the check while capacity is available. They are then all admitted, even though only some laterRequestcalls can be recorded; the return value from that recording is ignored. Use reservation/commit/rollback semantics or track in-flight reservations so successful traffic cannot exceed the configured quota. The current sequential regression test will not catch this race.🤖 Prompt for AI Agents
🎯 Functional Correctness | 🟠 Major | ⚡ Quick win
🧩 Analysis chain
🏁 Script executed:
Repository: QuantumNous/new-api
Length of output: 9826
🏁 Script executed:
Repository: QuantumNous/new-api
Length of output: 321
🏁 Script executed:
Repository: QuantumNous/new-api
Length of output: 7278
🏁 Script executed:
Repository: QuantumNous/new-api
Length of output: 5615
🏁 Script executed:
Repository: QuantumNous/new-api
Length of output: 370
🏁 Script executed:
Repository: QuantumNous/new-api
Length of output: 219
Guard the in-memory success limiter when
successMaxCountis zero.In-memory handling mirrors the Redis zero-value contract for total limits, but the success path calls
CanRequestandRequestwithsuccessMaxCount == 0, so a single successful request records an entry and subsequent successful requests are rejected until expiry. Skip both calls whensuccessMaxCount == 0, or document and enforce a 0-as-1 request limit consistently.🤖 Prompt for AI Agents