# vim:set ts=4 sts=4 sw=4 et ft=: use Test::Nginx::Socket::Lua; use Cwd qw(cwd); #repeat_each(2); plan tests => repeat_each() * (blocks() * 3) + 2; 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; lua_shared_dict ipc_shm 1m; }; run_tests(); __DATA__ === TEST 1: set() errors if no ipc --- 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 ok, err = pcall(cache.set, cache, "foo") ngx.say(err) } } --- request GET /t --- response_body no ipc to propagate update, specify opts.ipc_shm or opts.ipc --- no_error_log [error] === TEST 2: set() validates key --- 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", { ipc_shm = "ipc_shm", })) local ok, err = pcall(cache.set, cache) if not ok then ngx.say(err) end } } --- request GET /t --- response_body key must be a string --- no_error_log [error] === TEST 3: set() puts a value directly in 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", { ipc_shm = "ipc_shm", })) -- setting a value in shm assert(cache:set("my_key", nil, 123)) -- declaring a callback that MUST NOT be called local function cb() ngx.log(ngx.ERR, "callback was called but should not have") end -- try to get() local value = assert(cache:get("my_key", nil, cb)) ngx.say("value from get(): ", value) -- value MUST BE in lru local value_lru = cache.lru:get("my_key") ngx.say("cache lru value after get(): ", value_lru) } } --- request GET /t --- response_body value from get(): 123 cache lru value after get(): 123 --- no_error_log [error] === TEST 4: set() puts a negative hit directly in shm_miss if specified --- 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", { ipc_shm = "ipc_shm", shm_miss = "cache_shm_miss", })) -- setting a value in shm assert(cache:set("my_key", nil, nil)) -- declaring a callback that MUST NOT be called local function cb() ngx.log(ngx.ERR, "callback was called but should not have") end -- try to get() local value, err = cache:get("my_key", nil, cb) if err then ngx.log(ngx.ERR, err) return end ngx.say("value from get(): ", value) } } --- request GET /t --- response_body value from get(): nil --- no_error_log [error] === TEST 5: set() puts a value directly in its own 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", { ipc_shm = "ipc_shm", })) -- setting a value in shm assert(cache:set("my_key", nil, 123)) -- value MUST BE be in lru local value_lru = cache.lru:get("my_key") ngx.say("cache lru value after set(): ", value_lru) } } --- request GET /t --- response_body cache lru value after set(): 123 --- no_error_log [error] === TEST 6: set() respects 'ttl' for non-nil values --- 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", { ipc_shm = "ipc_shm", })) -- setting a non-nil value in shm assert(cache:set("my_key", { ttl = 0.2, neg_ttl = 1, }, 123)) -- declaring a callback that logs accesses local function cb() ngx.say("callback called") return 123 end -- try to get() (callback MUST NOT be called) ngx.say("calling get()") local value = assert(cache:get("my_key", nil, cb)) ngx.say("value from get(): ", value) -- wait until expiry ngx.say("waiting until expiry...") ngx.sleep(0.3) ngx.say("waited 0.3s") -- try to get() (callback MUST be called) ngx.say("calling get()") local value = assert(cache:get("my_key", nil, cb)) ngx.say("value from get(): ", value) } } --- request GET /t --- response_body calling get() value from get(): 123 waiting until expiry... waited 0.3s calling get() callback called value from get(): 123 --- no_error_log [error] === TEST 7: set() respects 'neg_ttl' for nil values --- 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", { ipc_shm = "ipc_shm", })) -- setting a nil value in shm assert(cache:set("my_key", { ttl = 1, neg_ttl = 0.2, }, nil)) -- declaring a callback that logs accesses local function cb() ngx.say("callback called") return nil end -- try to get() (callback MUST NOT be called) ngx.say("calling get()") local value, err = cache:get("my_key", nil, cb) if err then ngx.log(ngx.ERR, err) end ngx.say("value from get(): ", value) -- wait until expiry ngx.say("waiting until expiry...") ngx.sleep(0.3) ngx.say("waited 0.3s") -- try to get() (callback MUST be called) ngx.say("calling get()") local value, err = cache:get("my_key", nil, cb) if err then ngx.log(ngx.ERR, err) end ngx.say("value from get(): ", value) } } --- request GET /t --- response_body calling get() value from get(): nil waiting until expiry... waited 0.3s calling get() callback called value from get(): nil --- no_error_log [error] === TEST 8: set() respects 'set_shm_tries' --- 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", { ipc_shm = "ipc_shm", })) local data, err = cache:set("key", { shm_set_tries = 5, }, string.rep("a", 2^12)) if err then ngx.log(ngx.ERR, err) return end -- from shm cache.lru:delete("key") local cb_called local function cb() cb_called = true end 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_called ~= nil) } } --- request GET /t --- response_body type of data in shm: string callback was called: false --- no_error_log [warn] [error] === TEST 9: set() with shm_miss can set a nil where a value was --- 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", { ipc_shm = "ipc_shm", shm_miss = "cache_shm_miss", })) local function cb() return 123 end -- install a non-nil value in the cache local value, err = cache:get("my_key", nil, cb) if err then ngx.log(ngx.ERR, err) return end ngx.say("initial value from get(): ", value) -- override that value with a negative hit that -- must go in the shm_miss (and the shm value must be -- erased) assert(cache:set("my_key", nil, nil)) -- and remove it from the LRU cache.lru:delete("my_key") -- ok, now we should be getting nil from the cache local value, err = cache:get("my_key", nil, cb) if err then ngx.log(ngx.ERR, err) return end ngx.say("value from get() after set(): ", value) } } --- request GET /t --- response_body initial value from get(): 123 value from get() after set(): nil --- no_error_log [error] === TEST 10: set() with shm_miss can set a value where a nil was --- 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", { ipc_shm = "ipc_shm", shm_miss = "cache_shm_miss", })) local function cb() return nil end -- install a non-nil value in the cache local value, err = cache:get("my_key", nil, cb) if err then ngx.log(ngx.ERR, err) return end ngx.say("initial value from get(): ", value) -- override that value with a negative hit that -- must go in the shm_miss (and the shm value must be -- erased) assert(cache:set("my_key", nil, 123)) -- and remove it from the LRU cache.lru:delete("my_key") -- ok, now we should be getting nil from the cache local value, err = cache:get("my_key", nil, cb) if err then ngx.log(ngx.ERR, err) return end ngx.say("value from get() after set(): ", value) } } --- request GET /t --- response_body initial value from get(): nil value from get() after set(): 123 --- no_error_log [error] === TEST 11: set() returns 'no memory' errors upon fragmentation in the 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", { ipc_shm = "ipc_shm", })) -- fill shm local idx = 0 while true do local ok, err, forcible = ngx.shared.cache_shm:set(idx, true) if not ok then ngx.log(ngx.ERR, err) return end if forcible then break end idx = idx + 1 end -- set large value local ok, err = cache:set("my_key", { shm_set_tries = 1 }, string.rep("a", 2^10)) ngx.say(ok) ngx.say(err) } } --- request GET /t --- response_body nil could not write to lua_shared_dict 'cache_shm': no memory --- no_error_log [error] [warn] === TEST 12: set() does not set LRU upon shm insertion error --- 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", { ipc_shm = "ipc_shm", })) -- fill shm local idx = 0 while true do local ok, err, forcible = ngx.shared.cache_shm:set(idx, true) if not ok then ngx.log(ngx.ERR, err) return end if forcible then break end idx = idx + 1 end -- set large value local ok = cache:set("my_key", { shm_set_tries = 1 }, string.rep("a", 2^10)) assert(ok == nil) local data = cache.lru:get("my_key") ngx.say(data) } } --- request GET /t --- response_body nil --- no_error_log [error] === TEST 13: set() calls broadcast() with invalidated key --- 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", { ipc = { register_listeners = function() end, broadcast = function(channel, data, ...) ngx.say("channel: ", channel) ngx.say("data: ", data) ngx.say("other args:", ...) return true end, poll = function() end, } })) assert(cache:set("my_key", nil, nil)) } } --- request GET /t --- response_body channel: mlcache:invalidations:my_mlcache data: my_key other args: --- no_error_log [error]