Files
2026-07-13 12:32:21 +08:00

2715 lines
66 KiB
Perl

# vim:set ts=4 sts=4 sw=4 et ft=:
use Test::Nginx::Socket::Lua;
use Cwd qw(cwd);
use lib '.';
use t::Util;
no_long_string();
workers(2);
#repeat_each(2);
plan tests => repeat_each() * (blocks() * 3) + 9;
my $pwd = cwd();
our $HttpConfig = qq{
lua_package_path "$pwd/lib/?.lua;;";
lua_shared_dict cache_shm 1m;
lua_shared_dict cache_shm_miss 1m;
init_by_lua_block {
-- local verbose = true
local verbose = false
local outfile = "$Test::Nginx::Util::ErrLogFile"
-- local outfile = "/tmp/v.log"
if verbose then
local dump = require "jit.dump"
dump.on(nil, outfile)
else
local v = require "jit.v"
v.on(outfile)
end
require "resty.core"
-- jit.opt.start("hotloop=1")
-- jit.opt.start("loopunroll=1000000")
-- jit.off()
}
};
run_tests();
__DATA__
=== TEST 1: get() validates key
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local ok, err = pcall(cache.get, cache)
if not ok then
ngx.say(err)
end
}
}
--- request
GET /t
--- response_body
key must be a string
--- no_error_log
[error]
=== TEST 2: get() accepts callback as nil or function
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local ok, err = pcall(cache.get, cache, "key", nil, nil)
if not ok then
ngx.say(err)
end
local ok, err = pcall(cache.get, cache, "key", nil, function() end)
if not ok then
ngx.say(err)
end
}
}
--- request
GET /t
--- response_body
--- no_error_log
[error]
=== TEST 3: get() rejects callbacks not nil or function
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local ok, err = pcall(cache.get, cache, "key", nil, "not a function")
if not ok then
ngx.say(err)
end
local ok, err = pcall(cache.get, cache, "key", nil, false)
if not ok then
ngx.say(err)
end
}
}
--- request
GET /t
--- response_body
callback must be nil or a function
callback must be nil or a function
--- no_error_log
[error]
=== TEST 4: get() validates opts
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local ok, err = pcall(cache.get, cache, "key", "opts")
if not ok then
ngx.say(err)
end
}
}
--- request
GET /t
--- response_body
opts must be a table
--- no_error_log
[error]
=== TEST 5: get() calls callback in protected mode with stack traceback
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb()
error("oops")
end
local data, err = cache:get("key", nil, cb)
if err then
ngx.say(err)
end
}
}
--- request
GET /t
--- response_body_like chomp
callback threw an error: .*? oops
stack traceback:
\s+\[C\]: in function 'error'
\s+content_by_lua\(nginx\.conf:\d+\):\d+: in function
--- no_error_log
[error]
=== TEST 6: get() is resilient to callback runtime errors with non-string arguments
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local data, err = cache:get("key", nil, function() error(ngx.null) end)
if err then
ngx.say(err)
end
local data, err = cache:get("key", nil, function() error({}) end)
if err then
ngx.say(err)
end
}
}
--- request
GET /t
--- response_body_like
callback threw an error: userdata: NULL
callback threw an error: table: 0x[0-9a-fA-F]+
--- no_error_log
[error]
=== TEST 7: get() caches a number
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb()
return 123
end
-- from callback
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from callback: ", type(data), " ", data)
-- from lru
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from lru: ", type(data), " ", data)
-- from shm
cache.lru:delete("key")
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from shm: ", type(data), " ", data)
}
}
--- request
GET /t
--- response_body
from callback: number 123
from lru: number 123
from shm: number 123
--- no_error_log
[error]
=== TEST 8: get() caches a boolean (true)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb()
return true
end
-- from callback
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from callback: ", type(data), " ", data)
-- from lru
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from lru: ", type(data), " ", data)
-- from shm
cache.lru:delete("key")
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from shm: ", type(data), " ", data)
}
}
--- request
GET /t
--- response_body
from callback: boolean true
from lru: boolean true
from shm: boolean true
--- no_error_log
[error]
=== TEST 9: get() caches a boolean (false)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb()
return false
end
-- from callback
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from callback: ", type(data), " ", data)
-- from lru
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from lru: ", type(data), " ", data)
-- from shm
cache.lru:delete("key")
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from shm: ", type(data), " ", data)
}
}
--- request
GET /t
--- response_body
from callback: boolean false
from lru: boolean false
from shm: boolean false
--- no_error_log
[error]
=== TEST 10: get() caches nil
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb()
return nil
end
-- from callback
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from callback: ", type(data), " ", data)
-- from lru
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from lru: ", type(data), " ", data)
-- from shm
cache.lru:delete("key")
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from shm: ", type(data), " ", data)
}
}
--- request
GET /t
--- response_body
from callback: nil nil
from lru: nil nil
from shm: nil nil
--- no_error_log
[error]
=== TEST 11: get() caches nil in 'shm_miss' if specified
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local dict = ngx.shared.cache_shm
local dict_miss = ngx.shared.cache_shm_miss
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
shm_miss = "cache_shm_miss"
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
-- from callback
local data, err = cache:get("key", nil, function() return nil end)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from callback: ", type(data), " ", data)
-- direct shm checks
-- concat key since shm values are namespaced per their the
-- mlcache name
local key = "my_mlcachekey"
local v, err = dict:get(key)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("no value in shm: ", v == nil)
local v, err = dict_miss:get(key)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("value in shm_miss is a sentinel nil value: ", v ~= nil)
-- subsequent calls from shm
cache.lru:delete("key")
-- here, we return 'true' and not nil in the callback. this is to
-- ensure that get() will check the shm_miss shared dict and read
-- the nil sentinel value in there, thus will not call the
-- callback.
local data, err = cache:get("key", nil, function() return true end)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from shm: ", type(data), " ", data)
-- from lru
local v = cache.lru:get("key")
ngx.say("value in lru is a sentinel nil value: ", v ~= nil)
}
}
--- request
GET /t
--- response_body
from callback: nil nil
no value in shm: true
value in shm_miss is a sentinel nil value: true
from shm: nil nil
value in lru is a sentinel nil value: true
--- no_error_log
[error]
=== TEST 12: get() caches a string
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb()
return "hello world"
end
-- from callback
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from callback: ", type(data), " ", data)
-- from lru
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from lru: ", type(data), " ", data)
-- from shm
cache.lru:delete("key")
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from shm: ", type(data), " ", data)
}
}
--- request
GET /t
--- response_body
from callback: string hello world
from lru: string hello world
from shm: string hello world
--- no_error_log
[error]
=== TEST 13: get() caches a table
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local cjson = require "cjson"
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb()
return {
hello = "world",
subt = { foo = "bar" }
}
end
-- from callback
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from callback: ", type(data), " ", data.hello, " ", data.subt.foo)
-- from lru
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from lru: ", type(data), " ", data.hello, " ", data.subt.foo)
-- from shm
cache.lru:delete("key")
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("from shm: ", type(data), " ", data.hello, " ", data.subt.foo)
}
}
--- request
GET /t
--- response_body
from callback: table world bar
from lru: table world bar
from shm: table world bar
--- no_error_log
[error]
=== TEST 14: get() errors when caching an unsupported type
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local cjson = require "cjson"
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb()
return ngx.null
end
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
}
}
--- request
GET /t
--- error_code: 500
--- error_log eval
qr/\[error\] .*?init\.lua:\d+: cannot cache value of type userdata/
=== TEST 15: get() calls callback with args
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb(a, b)
return a + b
end
local data, err = cache:get("key", nil, cb, 1, 2)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say(data)
}
}
--- request
GET /t
--- response_body
3
--- no_error_log
[error]
=== TEST 16: get() caches hit for 'ttl' from LRU (in ms)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", { ttl = 0.3 }))
local function cb()
ngx.say("in callback")
return 123
end
local data = assert(cache:get("key", nil, cb))
assert(data == 123)
ngx.sleep(0.2)
data = assert(cache:get("key", nil, cb))
assert(data == 123)
ngx.sleep(0.2)
data = assert(cache:get("key", nil, cb))
assert(data == 123)
}
}
--- request
GET /t
--- response_body
in callback
in callback
--- no_error_log
[error]
=== TEST 17: get() caches miss (nil) for 'neg_ttl' from LRU (in ms)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ttl = 10,
neg_ttl = 0.3
}))
local function cb()
ngx.say("in callback")
return nil
end
local data, err = cache:get("key", nil, cb)
assert(err == nil, err)
assert(data == nil)
ngx.sleep(0.2)
data, err = cache:get("key", nil, cb)
assert(err == nil, err)
assert(data == nil)
ngx.sleep(0.2)
data, err = cache:get("key", nil, cb)
assert(err == nil, err)
assert(data == nil)
}
}
--- request
GET /t
--- response_body
in callback
in callback
--- no_error_log
[error]
=== TEST 18: get() caches for 'opts.ttl' from LRU (in ms)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", { ttl = 10 }))
local function cb()
ngx.say("in callback")
return 123
end
local data = assert(cache:get("key", { ttl = 0.3 }, cb))
assert(data == 123)
ngx.sleep(0.2)
data = assert(cache:get("key", nil, cb))
assert(data == 123)
ngx.sleep(0.2)
data = assert(cache:get("key", nil, cb))
assert(data == 123)
}
}
--- request
GET /t
--- response_body
in callback
in callback
--- no_error_log
[error]
=== TEST 19: get() caches for 'opts.neg_ttl' from LRU (in ms)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", { neg_ttl = 2 }))
local function cb()
ngx.say("in callback")
return nil
end
local data, err = cache:get("key", { neg_ttl = 0.3 }, cb)
assert(err == nil, err)
assert(data == nil)
ngx.sleep(0.2)
data, err = cache:get("key", nil, cb)
assert(err == nil, err)
assert(data == nil)
ngx.sleep(0.2)
data, err = cache:get("key", nil, cb)
assert(err == nil, err)
assert(data == nil)
}
}
--- request
GET /t
--- response_body
in callback
in callback
--- no_error_log
[error]
=== TEST 20: get() with ttl of 0 means indefinite caching
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", { ttl = 0.3 }))
local function cb()
ngx.say("in callback")
return 123
end
local data = assert(cache:get("key", { ttl = 0 }, cb))
assert(data == 123)
ngx.sleep(0.4)
-- still in LRU
local data, stale = cache.lru:get("key")
if stale then
ngx.say("in LRU after 1.1s: stale")
else
ngx.say("in LRU after exp: ", data)
end
cache.lru:delete("key")
-- still in shm
data = assert(cache:get("key", nil, cb))
ngx.say("in shm after exp: ", data)
}
}
--- request
GET /t
--- response_body
in callback
in LRU after exp: 123
in shm after exp: 123
--- no_error_log
[error]
=== TEST 21: get() with neg_ttl of 0 means indefinite caching for nil values
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = assert(mlcache.new("my_mlcache", "cache_shm", { ttl = 0.3 }))
local function cb()
ngx.say("in callback")
return nil
end
local data, err = cache:get("key", { neg_ttl = 0 }, cb)
assert(err == nil, err)
assert(data == nil)
ngx.sleep(0.4)
-- still in LRU
local data, stale = cache.lru:get("key")
if stale then
ngx.say("in LRU after 0.4s: stale")
else
ngx.say("in LRU after exp: ", tostring(data))
end
cache.lru:delete("key")
-- still in shm
data, err = cache:get("key", nil, cb)
assert(err == nil, err)
ngx.say("in shm after exp: ", tostring(data))
}
}
--- request
GET /t
--- response_body_like
in callback
in LRU after exp: table: \S+
in shm after exp: nil
--- no_error_log
[error]
=== TEST 22: get() errors when ttl < 0
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb()
ngx.say("in callback")
return 123
end
local ok, err = pcall(cache.get, cache, "key", { ttl = -1 }, cb)
if not ok then
ngx.say(err)
end
}
}
--- request
GET /t
--- response_body
opts.ttl must be >= 0
--- no_error_log
[error]
=== TEST 23: get() errors when neg_ttl < 0
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local function cb()
ngx.say("in callback")
return 123
end
local ok, err = pcall(cache.get, cache, "key", { neg_ttl = -1 }, cb)
if not ok then
ngx.say(err)
end
}
}
--- request
GET /t
--- response_body
opts.neg_ttl must be >= 0
--- no_error_log
[error]
=== TEST 24: get() shm -> LRU caches for 'opts.ttl - since' in ms
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb()
return 123
end
local data = assert(cache:get("key", { ttl = 0.5 }, cb))
assert(data == 123)
ngx.sleep(0.2)
-- delete from LRU
cache.lru:delete("key")
-- from shm, setting LRU with smaller ttl
data, err = assert(cache:get("key", nil, cb))
assert(data == 123)
ngx.sleep(0.2)
-- still in LRU
local data, stale = cache.lru:get("key")
if stale then
ngx.say("is stale in LRU: ", stale)
else
ngx.say("is not expired in LRU: ", data)
end
ngx.sleep(0.1)
-- expired in LRU
local data, stale = cache.lru:get("key")
if stale then
ngx.say("is stale in LRU: ", stale)
else
ngx.say("is not expired in LRU: ", data)
end
}
}
--- request
GET /t
--- response_body
is not expired in LRU: 123
is stale in LRU: 123
--- no_error_log
[error]
=== TEST 25: get() shm -> LRU caches non-nil for 'indefinite' if ttl is 0
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb()
return 123
end
local data = assert(cache:get("key", { ttl = 0 }, cb))
assert(data == 123)
ngx.sleep(0.2)
-- delete from LRU
cache.lru:delete("key")
-- from shm, setting LRU with indefinite ttl too
data, err = assert(cache:get("key", nil, cb))
assert(data == 123)
-- still in LRU
local data, stale = cache.lru:get("key")
if stale then
ngx.say("is stale in LRU: ", stale)
else
ngx.say("is not expired in LRU: ", data)
end
}
}
--- request
GET /t
--- response_body
is not expired in LRU: 123
--- no_error_log
[error]
=== TEST 26: get() shm -> LRU caches for 'opts.neg_ttl - since' in ms
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb()
return nil
end
local data, err = cache:get("key", { neg_ttl = 0.5 }, cb)
assert(err == nil, err)
assert(data == nil)
ngx.sleep(0.2)
-- delete from LRU
cache.lru:delete("key")
-- from shm, setting LRU with smaller ttl
data, err = cache:get("key", nil, cb)
assert(err == nil, err)
assert(data == nil)
ngx.sleep(0.2)
-- still in LRU
local data, stale = cache.lru:get("key")
if stale then
ngx.say("is stale in LRU: ", tostring(stale))
else
ngx.say("is not expired in LRU: ", tostring(data))
end
ngx.sleep(0.1)
-- expired in LRU
local data, stale = cache.lru:get("key")
if stale then
ngx.say("is stale in LRU: ", tostring(stale))
else
ngx.say("is not expired in LRU: ", tostring(data))
end
}
}
--- request
GET /t
--- response_body_like
is not expired in LRU: table: \S+
is stale in LRU: table: \S+
--- no_error_log
[error]
=== TEST 27: get() shm -> LRU caches nil for 'indefinite' if neg_ttl is 0
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb()
return nil
end
local data, err =cache:get("key", { neg_ttl = 0 }, cb)
assert(err == nil)
assert(data == nil)
ngx.sleep(0.2)
-- delete from LRU
cache.lru:delete("key")
-- from shm, setting LRU with indefinite ttl too
data, err = cache:get("key", nil, cb)
assert(err == nil)
assert(data == nil)
-- still in LRU
local data, stale = cache.lru:get("key")
ngx.say("is stale in LRU: ", stale)
-- data is a table (nil sentinel value) so rely on stale instead
}
}
--- request
GET /t
--- response_body
is stale in LRU: nil
--- no_error_log
[error]
=== TEST 28: get() returns hit level
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb()
return 123
end
local _, _, hit_lvl = assert(cache:get("key", nil, cb))
ngx.say("hit level from callback: ", hit_lvl)
_, _, hit_lvl = assert(cache:get("key", nil, cb))
ngx.say("hit level from LRU: ", hit_lvl)
-- delete from LRU
cache.lru:delete("key")
_, _, hit_lvl = assert(cache:get("key", nil, cb))
ngx.say("hit level from shm: ", hit_lvl)
}
}
--- request
GET /t
--- response_body
hit level from callback: 3
hit level from LRU: 1
hit level from shm: 2
--- no_error_log
[error]
=== TEST 29: get() returns hit level for nil hits
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb()
return nil
end
local _, _, hit_lvl = cache:get("key", nil, cb)
ngx.say("hit level from callback: ", hit_lvl)
_, _, hit_lvl = cache:get("key", nil, cb)
ngx.say("hit level from LRU: ", hit_lvl)
-- delete from LRU
cache.lru:delete("key")
_, _, hit_lvl = cache:get("key", nil, cb)
ngx.say("hit level from shm: ", hit_lvl)
}
}
--- request
GET /t
--- response_body
hit level from callback: 3
hit level from LRU: 1
hit level from shm: 2
--- no_error_log
[error]
=== TEST 30: get() returns hit level for boolean false hits
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb()
return false
end
local _, _, hit_lvl = cache:get("key", nil, cb)
ngx.say("hit level from callback: ", hit_lvl)
_, _, hit_lvl = cache:get("key", nil, cb)
ngx.say("hit level from LRU: ", hit_lvl)
-- delete from LRU
cache.lru:delete("key")
_, _, hit_lvl = cache:get("key", nil, cb)
ngx.say("hit level from shm: ", hit_lvl)
}
}
--- request
GET /t
--- response_body
hit level from callback: 3
hit level from LRU: 1
hit level from shm: 2
--- no_error_log
[error]
=== TEST 31: get() JITs when hit coming from LRU
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb()
return 123456
end
for i = 1, 10e3 do
local data = assert(cache:get("key", nil, cb))
assert(data == 123456)
end
}
}
--- request
GET /t
--- response_body
--- error_log eval
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):10 loop\]/
--- no_error_log
[error]
=== TEST 32: get() JITs when hit of scalar value coming from shm
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb_number()
return 123456
end
local function cb_string()
return "hello"
end
local function cb_bool()
return false
end
for i = 1, 10e2 do
local data, err, hit_lvl = assert(cache:get("number", nil, cb_number))
assert(err == nil)
assert(data == 123456)
assert(hit_lvl == (i == 1 and 3 or 2))
cache.lru:delete("number")
end
for i = 1, 10e2 do
local data, err, hit_lvl = assert(cache:get("string", nil, cb_string))
assert(err == nil)
assert(data == "hello")
assert(hit_lvl == (i == 1 and 3 or 2))
cache.lru:delete("string")
end
for i = 1, 10e2 do
local data, err, hit_lvl = cache:get("bool", nil, cb_bool)
assert(err == nil)
assert(data == false)
assert(hit_lvl == (i == 1 and 3 or 2))
cache.lru:delete("bool")
end
}
}
--- request
GET /t
--- response_body
--- error_log eval
[
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):18 loop\]/,
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):27 loop\]/,
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):36 loop\]/,
]
--- no_error_log
[error]
=== TEST 33: get() JITs when hit of table value coming from shm
--- SKIP: blocked until l2_serializer
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb_table()
return { hello = "world" }
end
for i = 1, 10e2 do
local data = assert(cache:get("table", nil, cb_table))
assert(type(data) == "table")
assert(data.hello == "world")
cache.lru:delete("table")
end
}
}
--- request
GET /t
--- response_body
--- error_log eval
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):18 loop\]/
--- no_error_log
[error]
=== TEST 34: get() JITs when miss coming from LRU
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb()
return nil
end
for i = 1, 10e3 do
local data, err = cache:get("key", nil, cb)
assert(err == nil)
assert(data == nil)
end
}
}
--- request
GET /t
--- response_body
--- error_log eval
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):10 loop\]/
--- no_error_log
[error]
=== TEST 35: get() JITs when miss coming from shm
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb()
return nil
end
for i = 1, 10e3 do
local data, err = cache:get("key", nil, cb)
assert(err == nil)
assert(data == nil)
cache.lru:delete("key")
end
}
}
--- request
GET /t
--- response_body
--- error_log eval
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):10 loop\]/
--- no_error_log
[error]
=== TEST 36: get() callback can return nil + err (string)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb()
return nil, "an error occurred"
end
local data, err = cache:get("1", nil, cb)
if err then
ngx.say("cb return values: ", data, " ", err)
end
local function cb2()
-- we will return "foo" to users as well from get(), and
-- not just nil, if they wish so.
return "foo", "an error occurred again"
end
data, err = cache:get("2", nil, cb2)
if err then
ngx.say("cb2 return values: ", data, " ", err)
end
}
}
--- request
GET /t
--- response_body
cb return values: nil an error occurred
cb2 return values: foo an error occurred again
--- no_error_log
[error]
=== TEST 37: get() callback can return nil + err (non-string) safely
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local function cb()
return nil, { err = "an error occurred" } -- invalid usage
end
local data, err = cache:get("1", nil, cb)
if err then
ngx.say("cb return values: ", data, " ", err)
end
local function cb2()
-- we will return "foo" to users as well from get(), and
-- not just nil, if they wish so.
return "foo", { err = "an error occurred again" } -- invalid usage
end
data, err = cache:get("2", nil, cb2)
if err then
ngx.say("cb2 return values: ", data, " ", err)
end
}
}
--- request
GET /t
--- response_body_like chomp
cb return values: nil table: 0x[[:xdigit:]]+
cb2 return values: foo table: 0x[[:xdigit:]]+
--- no_error_log
[error]
=== TEST 38: get() callback can return nil + err (table) and will call __tostring
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local mt = {
__tostring = function()
return "hello from __tostring"
end
}
local function cb()
return nil, setmetatable({}, mt)
end
local data, err = cache:get("1", nil, cb)
if err then
ngx.say("cb return values: ", data, " ", err)
end
}
}
--- request
GET /t
--- response_body
cb return values: nil hello from __tostring
--- no_error_log
[error]
=== TEST 39: get() callback's 3th return value can override the ttl
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local opts = { ttl = 10 }
local cache = assert(mlcache.new("my_mlcache", "cache_shm", opts))
local function cb()
ngx.say("in callback 1")
return 1, nil, 0.1
end
local function cb2()
ngx.say("in callback 2")
return 2
end
-- cache our value (runs cb)
local data, err = cache:get("key", opts, cb)
assert(err == nil, err)
assert(data == 1)
-- should not run cb2
data, err = cache:get("key", opts, cb2)
assert(err == nil, err)
assert(data == 1)
ngx.sleep(0.15)
-- should run cb2 (value expired)
data, err = cache:get("key", opts, cb2)
assert(err == nil, err)
assert(data == 2)
}
}
--- request
GET /t
--- response_body
in callback 1
in callback 2
--- no_error_log
[error]
=== TEST 40: get() callback's 3th return value can override the neg_ttl
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local opts = { ttl = 10, neg_ttl = 10 }
local cache = assert(mlcache.new("my_mlcache", "cache_shm", opts))
local function cb()
ngx.say("in callback 1")
return nil, nil, 0.1
end
local function cb2()
ngx.say("in callback 2")
return 1
end
-- cache our value (runs cb)
local data, err = cache:get("key", opts, cb)
assert(err == nil, err)
assert(data == nil)
-- should not run cb2
data, err = cache:get("key", opts, cb2)
assert(err == nil, err)
assert(data == nil)
ngx.sleep(0.15)
-- should run cb2 (value expired)
data, err = cache:get("key", opts, cb2)
assert(err == nil, err)
assert(data == 1)
}
}
--- request
GET /t
--- response_body
in callback 1
in callback 2
--- no_error_log
[error]
=== TEST 41: get() ignores invalid callback 3rd return value (not number)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local opts = { ttl = 0.1, neg_ttl = 0.1 }
local cache = assert(mlcache.new("my_mlcache", "cache_shm", opts))
local function pos_cb()
ngx.say("in positive callback")
return 1, nil, "success"
end
local function neg_cb()
ngx.say("in negative callback")
return nil, nil, {}
end
ngx.say("Test A: string TTL return value for positive data is ignored")
-- cache our value (runs pos_cb)
local data, err = cache:get("pos_key", opts, pos_cb)
assert(err == nil, err)
assert(data == 1)
-- neg_cb should not run
data, err = cache:get("pos_key", opts, neg_cb)
assert(err == nil, err)
assert(data == 1)
ngx.sleep(0.15)
-- should run neg_cb
data, err = cache:get("pos_key", opts, neg_cb)
assert(err == nil, err)
assert(data == nil)
ngx.say("Test B: table TTL return value for negative data is ignored")
-- cache our value (runs neg_cb)
data, err = cache:get("neg_key", opts, neg_cb)
assert(err == nil, err)
assert(data == nil)
-- pos_cb should not run
data, err = cache:get("neg_key", opts, pos_cb)
assert(err == nil, err)
assert(data == nil)
ngx.sleep(0.15)
-- should run pos_cb
data, err = cache:get("neg_key", opts, pos_cb)
assert(err == nil, err)
assert(data == 1)
}
}
--- request
GET /t
--- response_body
Test A: string TTL return value for positive data is ignored
in positive callback
in negative callback
Test B: table TTL return value for negative data is ignored
in negative callback
in positive callback
--- no_error_log
[error]
=== TEST 42: get() passes 'resty_lock_opts' for L3 calls
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local resty_lock = require "resty.lock"
local mlcache = require "kong.resty.mlcache"
local resty_lock_opts = { timeout = 5 }
do
local orig_resty_lock_new = resty_lock.new
resty_lock.new = function(_, dict_name, opts, ...)
ngx.say("was given 'opts.resty_lock_opts': ", opts == resty_lock_opts)
return orig_resty_lock_new(_, dict_name, opts, ...)
end
end
local cache, err = mlcache.new("my_mlcache", "cache_shm", {
resty_lock_opts = resty_lock_opts,
})
if not cache then
ngx.log(ngx.ERR, err)
return
end
local data, err = cache:get("key", nil, function() return nil end)
if err then
ngx.log(ngx.ERR, err)
return
end
}
}
--- request
GET /t
--- response_body
was given 'opts.resty_lock_opts': true
--- no_error_log
[error]
=== TEST 43: get() errors on lock timeout
--- http_config eval: $::HttpConfig
--- config
location = /t {
access_by_lua_block {
ngx.shared.cache_shm:set(1, true, 0.2)
ngx.shared.cache_shm:set(2, true, 0.2)
}
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache_1 = assert(mlcache.new("my_mlcache", "cache_shm", {
ttl = 0.3
}))
local cache_2 = assert(mlcache.new("my_mlcache", "cache_shm", {
ttl = 0.3,
resty_lock_opts = {
timeout = 0.2
}
}))
local function cb(delay, return_val)
if delay then
ngx.sleep(delay)
end
return return_val or 123
end
-- cache in shm
local data, err, hit_lvl = cache_1:get("my_key", nil, cb)
assert(data == 123)
assert(err == nil)
assert(hit_lvl == 3)
-- make shm + LRU expire
ngx.sleep(0.3)
local t1 = ngx.thread.spawn(function()
-- trigger L3 callback again, but slow to return this time
cache_1:get("my_key", nil, cb, 0.3, 456)
end)
local t2 = ngx.thread.spawn(function()
-- make this mlcache wait on other's callback, and timeout
local data, err, hit_lvl = cache_2:get("my_key", nil, cb)
ngx.say("data: ", data)
ngx.say("err: ", err)
ngx.say("hit_lvl: ", hit_lvl)
end)
assert(ngx.thread.wait(t1))
assert(ngx.thread.wait(t2))
ngx.say()
ngx.say("-> subsequent get()")
data, err, hit_lvl = cache_2:get("my_key", nil, cb, nil, 123)
ngx.say("data: ", data)
ngx.say("err: ", err)
ngx.say("hit_lvl: ", hit_lvl) -- should be 1 since LRU instances are shared by mlcache namespace, and t1 finished
}
}
--- request
GET /t
--- response_body
data: nil
err: could not acquire callback lock: timeout
hit_lvl: nil
-> subsequent get()
data: 456
err: nil
hit_lvl: 1
--- no_error_log
[error]
=== TEST 44: get() returns data even if failed to set in shm
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local dict = ngx.shared.cache_shm
local mlcache = require "kong.resty.mlcache"
-- fill up shm
local idx = 0
while true do
local ok, err, forcible = dict:set(idx, string.rep("a", 2^5))
if not ok then
ngx.log(ngx.ERR, err)
return
end
if forcible then
break
end
idx = idx + 1
end
-- now, trigger a hit with a value many times as large
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local data, err = cache:get("key", nil, function()
return string.rep("a", 2^20)
end)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("data type: ", type(data))
}
}
--- request
GET /t
--- response_body
data type: string
--- error_log eval
qr/\[warn\] .*? could not write to lua_shared_dict 'cache_shm' after 3 tries \(no memory\), it is either/
--- no_error_log
[error]
=== TEST 45: get() errors on invalid opts.shm_set_tries
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache, err = mlcache.new("my_mlcache", "cache_shm")
if not cache then
ngx.log(ngx.ERR, err)
return
end
local values = {
"foo",
-1,
0,
}
for _, v in ipairs(values) do
local ok, err = pcall(cache.get, cache, "key", {
shm_set_tries = v
}, function() end)
if not ok then
ngx.say(err)
end
end
}
}
--- request
GET /t
--- response_body
opts.shm_set_tries must be a number
opts.shm_set_tries must be >= 1
opts.shm_set_tries must be >= 1
--- no_error_log
[error]
=== TEST 46: get() with default shm_set_tries to LRU evict items when a large value is being cached
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local dict = ngx.shared.cache_shm
dict:flush_all()
dict:flush_expired()
local mlcache = require "kong.resty.mlcache"
-- fill up shm
local idx = 0
while true do
local ok, err, forcible = dict:set(idx, string.rep("a", 2^2))
if not ok then
ngx.log(ngx.ERR, err)
return
end
if forcible then
break
end
idx = idx + 1
end
-- shm:set() will evict up to 30 items when the shm is full
-- now, trigger a hit with a larger value which should trigger LRU
-- eviction and force the slab allocator to free pages
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local cb_calls = 0
local function cb()
cb_calls = cb_calls + 1
return string.rep("a", 2^5)
end
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
-- from shm
cache.lru:delete("key")
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("type of data in shm: ", type(data))
ngx.say("callback was called: ", cb_calls, " times")
}
}
--- request
GET /t
--- response_body
type of data in shm: string
callback was called: 1 times
--- no_error_log
[warn]
[error]
=== TEST 47: get() respects instance opts.shm_set_tries to LRU evict items when a large value is being cached
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local dict = ngx.shared.cache_shm
dict:flush_all()
dict:flush_expired()
local mlcache = require "kong.resty.mlcache"
-- fill up shm
local idx = 0
while true do
local ok, err, forcible = dict:set(idx, string.rep("a", 2^2))
if not ok then
ngx.log(ngx.ERR, err)
return
end
if forcible then
break
end
idx = idx + 1
end
-- shm:set() will evict up to 30 items when the shm is full
-- now, trigger a hit with a larger value which should trigger LRU
-- eviction and force the slab allocator to free pages
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
shm_set_tries = 5
}))
local cb_calls = 0
local function cb()
cb_calls = cb_calls + 1
return string.rep("a", 2^12)
end
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
-- from shm
cache.lru:delete("key")
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("type of data in shm: ", type(data))
ngx.say("callback was called: ", cb_calls, " times")
}
}
--- request
GET /t
--- response_body
type of data in shm: string
callback was called: 1 times
--- no_error_log
[warn]
[error]
=== TEST 48: get() accepts opts.shm_set_tries to LRU evict items when a large value is being cached
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local dict = ngx.shared.cache_shm
dict:flush_all()
dict:flush_expired()
local mlcache = require "kong.resty.mlcache"
-- fill up shm
local idx = 0
while true do
local ok, err, forcible = dict:set(idx, string.rep("a", 2^2))
if not ok then
ngx.log(ngx.ERR, err)
return
end
if forcible then
break
end
idx = idx + 1
end
-- now, trigger a hit with a value ~3 times as large
-- which should trigger retries and eventually remove 9 other
-- cached items
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
local cb_calls = 0
local function cb()
cb_calls = cb_calls + 1
return string.rep("a", 2^12)
end
local data, err = cache:get("key", {
shm_set_tries = 5
}, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
-- from shm
cache.lru:delete("key")
local data, err = cache:get("key", nil, cb)
if err then
ngx.log(ngx.ERR, err)
return
end
ngx.say("type of data in shm: ", type(data))
ngx.say("callback was called: ", cb_calls, " times")
}
}
--- request
GET /t
--- response_body
type of data in shm: string
callback was called: 1 times
--- no_error_log
[warn]
[error]
=== TEST 49: get() caches data in L1 LRU even if failed to set in shm
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local dict = ngx.shared.cache_shm
dict:flush_all()
dict:flush_expired()
local mlcache = require "kong.resty.mlcache"
-- fill up shm
local idx = 0
while true do
local ok, err, forcible = dict:set(idx, string.rep("a", 2^2))
if not ok then
ngx.log(ngx.ERR, err)
return
end
if forcible then
break
end
idx = idx + 1
end
-- now, trigger a hit with a value many times as large
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ttl = 0.3,
shm_set_tries = 1,
}))
local data, err = cache:get("key", nil, function()
return string.rep("a", 2^20)
end)
if err then
ngx.log(ngx.ERR, err)
return
end
local data = cache.lru:get("key")
ngx.say("type of data in LRU: ", type(data))
ngx.say("sleeping...")
ngx.sleep(0.4)
local _, stale = cache.lru:get("key")
ngx.say("is stale: ", stale ~= nil)
}
}
--- request
GET /t
--- response_body
type of data in LRU: string
sleeping...
is stale: true
--- no_error_log
[error]
=== TEST 50: get() does not cache value in LRU indefinitely when retrieved from shm on last ms (see GH PR #58)
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm", {
ttl = 0.2,
}))
local lru = cache.lru
local function cb(v)
return v or 42
end
local data, err, hit_lvl = cache:get("key", nil, cb)
assert(data == 42, err or "invalid data value: " .. data)
ngx.say("hit_lvl: ", hit_lvl)
data, err, hit_lvl = cache:get("key", nil, cb)
assert(data == 42, err or "invalid data value: " .. data)
ngx.say("hit_lvl: ", hit_lvl)
lru:delete("key")
data, err, hit_lvl = cache:get("key", nil, cb)
assert(data == 42, err or "invalid data value: " .. data)
ngx.say("hit_lvl: ", hit_lvl)
data, err, hit_lvl = cache:get("key", nil, cb)
assert(data == 42, err or "invalid data value: " .. data)
ngx.say("hit_lvl: ", hit_lvl)
ngx.sleep(0.2)
data, err, hit_lvl = cache:get("key", nil, cb)
assert(data == 42, err or "invalid data value: " .. data)
ngx.say("hit_lvl: ", hit_lvl)
while true do
lru:delete("key")
data, err, hit_lvl = cache:get("key", nil, cb)
if hit_lvl == 3 then
assert(data == 42, err or "invalid data value: " .. data)
ngx.say("hit_lvl: ", hit_lvl)
break
end
ngx.sleep(0)
end
data, err, hit_lvl = cache:get("key", nil, cb)
assert(data == 42, err or "invalid data value: " .. data)
ngx.say("hit_lvl: ", hit_lvl)
ngx.sleep(0.201)
data, err, hit_lvl = cache:get("key", nil, cb, 91)
assert(data == 91, err or "invalid data value: " .. data)
ngx.say("hit_lvl: ", hit_lvl)
}
}
--- request
GET /t
--- response_body
hit_lvl: 3
hit_lvl: 1
hit_lvl: 2
hit_lvl: 1
hit_lvl: 3
hit_lvl: 3
hit_lvl: 1
hit_lvl: 3
--- no_error_log
[error]
=== TEST 51: get() bypass cache for negative callback TTL
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local opts = { ttl = 0.1, neg_ttl = 0.1 }
local cache = assert(mlcache.new("my_mlcache", "cache_shm", opts))
local function pos_cb()
ngx.say("in positive callback")
return 1, nil, -1
end
local function neg_cb()
ngx.say("in negative callback")
return nil, nil, -1
end
ngx.say("Test A: negative TTL return value for positive data bypasses cache")
-- don't cache our value (runs pos_cb)
local data, err, hit_level = cache:get("pos_key", opts, pos_cb)
assert(err == nil, err)
assert(data == 1)
assert(hit_level == 3)
-- pos_cb should run again
data, err, hit_level = cache:get("pos_key", opts, pos_cb)
assert(err == nil, err)
assert(data == 1)
assert(hit_level == 3)
ngx.say("Test B: negative TTL return value for negative data bypasses cache")
-- don't cache our value (runs neg_cb)
data, err, hit_level = cache:get("neg_key", opts, neg_cb)
assert(err == nil, err)
assert(data == nil)
assert(hit_level == 3)
-- neg_cb should run again
data, err, hit_level = cache:get("neg_key", opts, neg_cb)
assert(err == nil, err)
assert(data == nil)
assert(hit_level == 3)
}
}
--- request
GET /t
--- response_body
Test A: negative TTL return value for positive data bypasses cache
in positive callback
in positive callback
Test B: negative TTL return value for negative data bypasses cache
in negative callback
in negative callback
--- no_error_log
[error]
=== TEST 52: get() nil callback returns positive cached items from L1/L2
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
-- miss lookup
local data, err, hit_lvl = cache:get("key")
if err then
ngx.log(ngx.ERR, err)
end
ngx.say("-> miss")
ngx.say("data: ", data)
ngx.say("err: ", err)
ngx.say("hit_lvl: ", hit_lvl)
-- cache an item
local _, err = cache:get("key", nil, function() return 123 end)
if err then
ngx.log(ngx.ERR, err)
end
-- hit from lru
data, err, hit_lvl = cache:get("key")
ngx.say()
ngx.say("-> from LRU")
ngx.say("data: ", data)
ngx.say("err: ", err)
ngx.say("hit_lvl: ", hit_lvl)
-- hit from shm
cache.lru:delete("key")
data, err, hit_lvl = cache:get("key")
ngx.say()
ngx.say("-> from shm")
ngx.say("data: ", data)
ngx.say("err: ", err)
ngx.say("hit_lvl: ", hit_lvl)
-- promoted to lru again
data, err, hit_lvl = cache:get("key")
ngx.say()
ngx.say("-> promoted to LRU")
ngx.say("data: ", data)
ngx.say("err: ", err)
ngx.say("hit_lvl: ", hit_lvl)
}
}
--- request
GET /t
--- response_body
-> miss
data: nil
err: nil
hit_lvl: -1
-> from LRU
data: 123
err: nil
hit_lvl: 1
-> from shm
data: 123
err: nil
hit_lvl: 2
-> promoted to LRU
data: 123
err: nil
hit_lvl: 1
--- no_error_log
[error]
=== TEST 53: get() nil callback returns negative cached items from L1/L2
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
-- miss lookup
local data, err, hit_lvl = cache:get("key")
if err then
ngx.log(ngx.ERR, err)
end
ngx.say("-> miss")
ngx.say("data: ", data)
ngx.say("err: ", err)
ngx.say("hit_lvl: ", hit_lvl)
-- cache an item
local _, err = cache:get("key", nil, function() return nil end)
if err then
ngx.log(ngx.ERR, err)
end
-- hit from lru
data, err, hit_lvl = cache:get("key")
ngx.say()
ngx.say("-> from LRU")
ngx.say("data: ", data)
ngx.say("err: ", err)
ngx.say("hit_lvl: ", hit_lvl)
-- hit from shm
cache.lru:delete("key")
data, err, hit_lvl = cache:get("key")
ngx.say()
ngx.say("-> from shm")
ngx.say("data: ", data)
ngx.say("err: ", err)
ngx.say("hit_lvl: ", hit_lvl)
-- promoted to lru again
data, err, hit_lvl = cache:get("key")
ngx.say()
ngx.say("-> promoted to LRU")
ngx.say("data: ", data)
ngx.say("err: ", err)
ngx.say("hit_lvl: ", hit_lvl)
}
}
--- request
GET /t
--- response_body
-> miss
data: nil
err: nil
hit_lvl: -1
-> from LRU
data: nil
err: nil
hit_lvl: 1
-> from shm
data: nil
err: nil
hit_lvl: 2
-> promoted to LRU
data: nil
err: nil
hit_lvl: 1
--- no_error_log
[error]
=== TEST 54: get() JITs on misses without a callback
--- http_config eval: $::HttpConfig
--- config
location = /t {
content_by_lua_block {
local mlcache = require "kong.resty.mlcache"
local cache = assert(mlcache.new("my_mlcache", "cache_shm"))
for i = 1, 10e3 do
cache:get("key")
end
}
}
--- request
GET /t
--- ignore_response_body
--- error_log eval
qr/\[TRACE\s+\d+ content_by_lua\(nginx\.conf:\d+\):6 loop\]/
--- no_error_log
[error]