This is an automated email from the ASF dual-hosted git repository.
nic-6443 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 3978aa3c5 fix(limit-count): commit() must report the window-weighted
remaining (#13704)
3978aa3c5 is described below
commit 3978aa3c52c7d4d873c2ba5c6d48456cd92f2c28
Author: Nic <[email protected]>
AuthorDate: Mon Jul 20 12:13:59 2026 +0800
fix(limit-count): commit() must report the window-weighted remaining
(#13704)
---
.../limit-count/sliding-window/sliding-window.lua | 25 +++++++++-
t/plugin/limit-count-sliding.t | 58 ++++++++++++++++++++++
2 files changed, 81 insertions(+), 2 deletions(-)
diff --git a/apisix/plugins/limit-count/sliding-window/sliding-window.lua
b/apisix/plugins/limit-count/sliding-window/sliding-window.lua
index f21e5b8a4..7e6592627 100644
--- a/apisix/plugins/limit-count/sliding-window/sliding-window.lua
+++ b/apisix/plugins/limit-count/sliding-window/sliding-window.lua
@@ -169,6 +169,7 @@ end
function _M.commit(self, key, cost)
local now = ngx_now()
local counter_key = get_counter_key(self, key, now)
+ local last_counter_key = get_counter_key(self, key, now - self.window_size)
local remaining_time = self.window_size - now % self.window_size
local red_cli, err
@@ -179,18 +180,38 @@ function _M.commit(self, key, cost)
end
end
+ local red = self.red_cli or red_cli
local expiry = self.window_size * 2
local new_count
- new_count, err = self.store:incr(counter_key, cost, expiry, self.red_cli
or red_cli)
+ new_count, err = self.store:incr(counter_key, cost, expiry, red)
if err then
return nil, err, 0
end
+ -- the delta is already recorded at this point, so a failed read of the
+ -- previous window must not fail the commit -- the caller would retry it
+ -- and double-count the delta. Degrade to a remaining without the previous
+ -- window's share instead.
+ local last_count, last_err = self.store:get(last_counter_key, red)
+ if last_err then
+ log.error("failed to get the last window count: ", last_err)
+ end
+ if not last_count then
+ last_count = 0
+ end
+ if last_count > self.limit then
+ last_count = self.limit
+ end
+
if red_cli then
red_cli:set_keepalive(10000, 100)
end
- local remaining = math_floor(self.limit - new_count)
+ -- report the same window-weighted remaining as incoming(); reporting
+ -- limit - new_count alone hands delayed-sync callers a full budget at
+ -- every window start, degrading the sliding window to a fixed one
+ local estimated_last_window_count = last_count / self.window_size *
remaining_time
+ local remaining = math_floor(self.limit - new_count -
estimated_last_window_count)
return 0, remaining, round_off_decimal_places(remaining_time, 2)
end
diff --git a/t/plugin/limit-count-sliding.t b/t/plugin/limit-count-sliding.t
index e6c069a38..d85316943 100644
--- a/t/plugin/limit-count-sliding.t
+++ b/t/plugin/limit-count-sliding.t
@@ -336,3 +336,61 @@ accept 1 count 1
accept 1 count 2
accept 0 count 2
stored: 2
+
+
+
+=== TEST 9: commit() reports the window-weighted remaining, like incoming()
+# regression: commit() used to report limit - current_count, ignoring the
+# previous window's weighted share. Delayed sync caches that value as the
+# global quota, so every new window started from a full budget and the
+# sliding window degraded into a fixed window.
+--- timeout: 10
+--- config
+ location /t {
+ content_by_lua_block {
+ local sliding_window =
+
require("apisix.plugins.limit-count.sliding-window.sliding-window")
+ local redis_store =
+
require("apisix.plugins.limit-count.sliding-window.store.redis")
+ local redis_cli =
require("apisix.plugins.limit-count.util").redis_cli
+ local conf = {
+ redis_host = "127.0.0.1",
+ redis_port = 6379,
+ redis_database = 1,
+ }
+ local limit, window = 400, 3
+ local lim, err = sliding_window.new_with_red_cli_factory(
+ redis_store, limit, window, redis_cli, conf)
+ if not lim then
+ ngx.say("failed to create limiter: ", err)
+ return
+ end
+
+ -- wait for the first 0.4s of a window so the previous window's
+ -- weight stays within a known band during the call below, with
+ -- headroom for the redis round trips before commit() reads time
+ while ngx.now() % window >= 0.4 do
+ ngx.sleep(0.05)
+ end
+ ngx.update_time()
+
+ local now = ngx.now()
+ local key = "ut-commit-weight-" .. now
+ local last_wid = math.floor((now - window) / window)
+ local red = redis_cli(conf)
+ red:set(("%s.%s.counter"):format(key, last_wid), 300, "EX", 60)
+
+ local _, remaining = lim:commit(key, 20)
+ -- remaining_time is in (2.6, 3], so the previous window weighs
+ -- 300 / 3 * remaining_time = 260..300 and the remaining must be
+ -- 400 - 20 - (260..300) = 80..120; assert up to 130 to keep
+ -- headroom for scheduling delay between the wait and the call
+ if remaining >= 80 and remaining <= 130 then
+ ngx.say("ok")
+ else
+ ngx.say("unexpected remaining: ", remaining)
+ end
+ }
+ }
+--- response_body
+ok