-
Notifications
You must be signed in to change notification settings - Fork 170
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
[count-inc] Add decrement logic to the incoming function #843
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 |
---|---|---|
|
@@ -194,7 +194,7 @@ describe('Rate limit policy', function() | |
assert(rate_limit_policy:access(context)) | ||
assert.returns_error('limits exceeded', rate_limit_policy:access(context)) | ||
|
||
assert.equal('2', redis:get('11110_fixed_window_test3')) | ||
assert.equal('1', redis:get('11110_fixed_window_test3')) | ||
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. I don't fully understand the purpose of this change. 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.
First, we should not increase the number of hits when going over limits, because we should count the number the client calls the API backend. The previous behavior is the below: Case 1: fixed_window_limiters : [
{ "key" : { "name" : "a"}, "count" : 1, "window" : 10 },
{ "key" : { "name" : "b"}, "count" : 2, "window" : 10 }
] After 2 API requests, Case 2: fixed_window_limiters : [
{ "key" : { "name" : "a"}, "count" : 2, "window" : 10 },
{ "key" : { "name" : "b"}, "count" : 1, "window" : 10 }
] After 2 API requests, |
||
assert.spy(ngx.exit).was_called_with(429) | ||
end) | ||
|
||
|
@@ -211,7 +211,7 @@ describe('Rate limit policy', function() | |
assert(rate_limit_policy:access(ctx)) | ||
assert.returns_error('limits exceeded', rate_limit_policy:access(ctx)) | ||
|
||
assert.equal('2', redis:get('11110_fixed_window_test3')) | ||
assert.equal('1', redis:get('11110_fixed_window_test3')) | ||
assert.spy(ngx.exit).was_called_with(429) | ||
end) | ||
|
||
|
@@ -232,7 +232,7 @@ describe('Rate limit policy', function() | |
assert(rate_limit_policy:access(context)) | ||
assert.returns_error('limits exceeded', rate_limit_policy:access(context)) | ||
|
||
assert.equal('2', redis:get('11110_fixed_window_' .. test_host)) | ||
assert.equal('1', redis:get('11110_fixed_window_' .. test_host)) | ||
assert.spy(ngx.exit).was_called_with(429) | ||
end) | ||
|
||
|
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,152 @@ | ||
local _M = require 'resty.limit.count-inc' | ||
|
||
local shdict_mt = { | ||
__index = { | ||
get = function(t, k) return rawget(t, k) end, | ||
set = function(t, k, v) rawset(t, k , v); return true end, | ||
incr = function(t, key, inc, init, _) | ||
local value = t:get(key) or init | ||
if not value then return nil, 'not found' end | ||
|
||
t:set(key, value + inc) | ||
return t:get(key) | ||
end, | ||
} | ||
} | ||
local function shdict() | ||
return setmetatable({ }, shdict_mt) | ||
end | ||
|
||
describe('resty.limit.count-inc', function() | ||
|
||
before_each(function() | ||
ngx.shared.limiter = mock(shdict(), true) | ||
end) | ||
|
||
describe('.new', function() | ||
describe('using correct shdict', function() | ||
it('returns limiter', function() | ||
local lim = _M.new('limiter', 1, 1) | ||
assert.same(1, lim.limit) | ||
assert.same(1, lim.window) | ||
end) | ||
end) | ||
|
||
describe('using incorrect shdict', function() | ||
it('returns error', function() | ||
local _, err = _M.new('incorrect', 1, 1) | ||
assert.same('shared dict not found', err) | ||
end) | ||
end) | ||
end) | ||
|
||
describe('.incoming', function() | ||
local lim | ||
|
||
before_each(function() | ||
lim = _M.new('limiter', 1, 1) | ||
end) | ||
|
||
describe('when commit is true', function() | ||
describe('if count is less than limit', function() | ||
it('returns zero and remaining', function() | ||
local delay, err = lim:incoming('tmp', true) | ||
assert.same(0, delay) | ||
assert.same(0, err) | ||
end) | ||
|
||
describe('and incr method fails', function() | ||
it('returns error', function() | ||
ngx.shared.limiter.incr = function() | ||
return nil, 'something' | ||
end | ||
local delay, err = lim:incoming('tmp', true) | ||
assert.is_nil(delay) | ||
assert.same('something', err) | ||
end) | ||
end) | ||
end) | ||
|
||
describe('if count is greater than limit', function() | ||
it('return rejected', function() | ||
lim:incoming('tmp', true) | ||
local delay, err = lim:incoming('tmp', true) | ||
assert.is_nil(delay) | ||
assert.same('rejected', err) | ||
end) | ||
|
||
describe('and incr method fails', function() | ||
it('returns error', function() | ||
ngx.shared.limiter.incr = function(t, key, inc, init, _) | ||
local value = t:get(key) or init | ||
if not value then return nil, 'not found' end | ||
if inc == -1 then return nil, 'something' end | ||
t:set(key, value + inc) | ||
return t:get(key) | ||
end | ||
lim:incoming('tmp', true) | ||
local delay, err = lim:incoming('tmp', true) | ||
assert.is_nil(delay) | ||
assert.same('something', err) | ||
end) | ||
end) | ||
end) | ||
end) | ||
|
||
describe('when commit is false', function() | ||
describe('if count is less than limit', function() | ||
it('returns zero and remaining', function() | ||
local delay, err = lim:incoming('tmp', false) | ||
assert.same(0, delay) | ||
assert.same(0, err) | ||
end) | ||
end) | ||
|
||
describe('if count is greater than limit', function() | ||
it('return rejected', function() | ||
lim:incoming('tmp', true) | ||
local delay, err = lim:incoming('tmp', false) | ||
assert.is_nil(delay) | ||
assert.same('rejected', err) | ||
end) | ||
end) | ||
end) | ||
|
||
end) | ||
|
||
describe('.uncommit', function() | ||
local lim | ||
|
||
before_each(function() | ||
lim = _M.new('limiter', 1, 1) | ||
end) | ||
|
||
describe('when incr method succeeds', function() | ||
it('returns remaining', function() | ||
lim:incoming('tmp', true) | ||
local delay = lim:uncommit('tmp') | ||
assert.same(1, delay) | ||
end) | ||
end) | ||
|
||
describe('when incr method fails', function() | ||
describe('if key is not found', function() | ||
it('returns remaining', function() | ||
local delay = lim:uncommit('tmp') | ||
assert.same(1, delay) | ||
end) | ||
end) | ||
|
||
it('returns error', function() | ||
lim:incoming('tmp', true) | ||
ngx.shared.limiter.incr = function() | ||
return nil, 'something' | ||
end | ||
local delay, err = lim:uncommit('tmp') | ||
assert.is_nil(delay) | ||
assert.same('something', err) | ||
end) | ||
end) | ||
end) | ||
|
||
end) |
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.
I believe this should be done by the
.uncommit
that is being called byresty.limit.traffic
.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.
@mikz you mean
.uncommit
needs to be called here with the condition ofi == n
, right?https://github.com/openresty/lua-resty-limit-traffic/blob/master/lib/resty/limit/traffic.lua#L27-L29
However
resty.limit.conn
includes this.So we should choose which file we edit,
resty.limit.traffic
andresty.limit.conn
, orresty.limit.count-inc
.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.
@y-tabata I think we don't need to change any resty.limit library. We have to use it correctly.
resty.limit.traffic
calls:incoming(key, false)
until the last limiter which is called as:incoming(key, true)
. If any of those return an error, it is fine to just exit. Nothing was committed to the database.When all of them succeed, it tries to call again
:incoming(key, true)
, but in this case, if any of them fails then call:uncommit(key)
for all limiters that it already called:incoming
.So I'd suspect our limiters are not rolled back because they are not wrapped in one
resty.limit.traffic
.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.
Using the current
count-inc
, I think that when the last limiter, which is called as :incoming(key, true), only returns nil and "rejected", the last one is committed to the database.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.
Ok. I got it now. Implemented a fix to the upstream library: openresty/lua-resty-limit-traffic@c53f224 (openresty/lua-resty-limit-traffic#34)
I think we can just copy that here.