# 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]