Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 7 additions & 1 deletion apisix/plugins/limit-count/init.lua
Original file line number Diff line number Diff line change
Expand Up @@ -425,6 +425,7 @@ local function get_rules(ctx, conf)
end
return {
{
idx = 1,
count = count,
time_window = time_window,
key = conf.key,
Expand All @@ -451,6 +452,7 @@ local function get_rules(ctx, conf)
return nil, err2
end
core.table.insert(rules, {
idx = index,
count = count,
time_window = time_window,
key_type = "constant",
Expand Down Expand Up @@ -603,7 +605,11 @@ local function run_rate_limit(conf, rule, ctx, name, cost, dry_run)
lim:log_phase_incoming(key, commit_cost)
return
elseif enable_delayed_sync then
local extra_key = name .. '#' .. conf.policy
-- the delayed syncer flushes every queued key with its own limiter,
-- so each rule needs a separate syncer (queue and timer). Sharing one
-- per plugin instance would sync the keys of other rules through the
-- count/time_window of whichever rule started the timer.
local extra_key = name .. '#' .. conf.policy .. '#' .. rule.idx
if conf._vid then
extra_key = extra_key .. '#' .. conf._vid
end
Expand Down
134 changes: 134 additions & 0 deletions t/plugin/limit-count-redis-delayed-sync2.t
Original file line number Diff line number Diff line change
Expand Up @@ -271,3 +271,137 @@ Host: example-7.com
"X-RateLimit-Remaining: 0",
"X-RateLimit-Remaining: 0",
]



=== TEST 10: set route with multiple rules and delayed sync
--- config
location /t {
content_by_lua_block {
local t = require("lib.test_admin")
local code, body = t.test('/apisix/admin/routes/echo-multi',
ngx.HTTP_PUT,
[[{
"uri": "/echo",
"plugins": {
"limit-count": {
"policy": "redis",
"redis_host": "127.0.0.1",
"redis_port": 6379,
"redis_database": 2,
"sync_interval": 0.5,
"show_limit_quota_header": true,
"rejected_code": 503,
"rules": [
{
"count": 5,
"time_window": 10,
"key": "${remote_addr}_per_minute",
"header_prefix": "rule1"
},
{
"count": 100,
"time_window": 20,
"key": "${remote_addr}_per_hour",
"header_prefix": "rule2"
}
]
}
},
"host": "example-10.com",
"upstream": {
"type": "roundrobin",
"nodes": {
"127.0.0.1:1980": 1
}
}
}]]
)
if code >= 300 then
ngx.status = code
end
ngx.say(body)
}
}
--- response_body
passed



=== TEST 11: each rule keeps its own remaining after delayed sync
--- config
location /t {
content_by_lua_block {
local http = require("resty.http")
local httpc = http.new()

local function request()
local res = httpc:request_uri(
"http://127.0.0.1:" .. ngx.var.server_port .. "/echo", {
headers = {
Host = "example-10.com",
},
})
if not res then
return nil, "no response"
end
return res
end

local function header(res, name)
for k, v in pairs(res.headers) do
if k:lower() == name then
return tonumber(v)
end
end
return nil
end

local res, err = request()
if not res then
ngx.say("FAIL: request before sync: ", err)
return
end

local r1 = header(res, "x-rule1-ratelimit-remaining")
local r2 = header(res, "x-rule2-ratelimit-remaining")
if not r1 or not r2 then
ngx.say("FAIL: missing remaining headers before sync")
return
end

if r1 ~= 4 or r2 ~= 99 then
ngx.say("FAIL: unexpected remaining before sync, rule1=", r1,
", rule2=", r2)
return
end

-- wait for the delayed sync timer to flush the local deltas to Redis
ngx.sleep(1)

res, err = request()
if not res then
ngx.say("FAIL: request after sync: ", err)
return
end

r1 = header(res, "x-rule1-ratelimit-remaining")
r2 = header(res, "x-rule2-ratelimit-remaining")
if not r1 or not r2 then
ngx.say("FAIL: missing remaining headers after sync")
return
end

-- the second rule must not be synced through the first rule's
-- count/time_window
if r1 ~= 3 or r2 ~= 98 then
ngx.say("FAIL: rule counters corrupted after sync, rule1=", r1,
", rule2=", r2)
return
end

ngx.say("PASS")
}
}
--- response_body
PASS
Loading