From dec80683f785cc079414fec05e167ebe91befd88 Mon Sep 17 00:00:00 2001 From: Thibault Charbonnier Date: Fri, 16 Mar 2018 22:14:29 -0700 Subject: [PATCH 1/2] fix(mlcache) do not error out on 'no memory' When the shm is full and eviction must happen through the LRU mechanism, it could be that the removed values do not free a large enough amount of memory in the shm, thus leading `set()` to fail and return a "no memory" error. This can happen when values being stored have sizes of different orders of magnitude. We now ignore such errors, so that the user can still retrieve the data and benefit from it in the L1 cache which does not suffer from such a limitation. We print a warning notice when such errors occur, but a new `opts.quiet` option can disable this behavior. From #41 --- lib/resty/mlcache.lua | 31 +++++++- t/01-new.t | 28 ++++++- t/02-get.t | 169 +++++++++++++++++++++++++++++++++++++++++- 3 files changed, 222 insertions(+), 6 deletions(-) diff --git a/lib/resty/mlcache.lua b/lib/resty/mlcache.lua index 7ea8c7a3a..780a9ea6e 100644 --- a/lib/resty/mlcache.lua +++ b/lib/resty/mlcache.lua @@ -13,10 +13,12 @@ local find = string.find local type = type local pcall = pcall local error = error -local shared = ngx.shared local tostring = tostring local tonumber = tonumber local setmetatable = setmetatable +local shared = ngx.shared +local ngx_log = ngx.log +local WARN = ngx.WARN local LOCK_KEY_PREFIX = "lua-resty-mlcache:lock:" @@ -252,6 +254,10 @@ function _M.new(name, shm, opts) then error("opts.l1_serializer must be a function", 2) end + + if opts.quiet ~= nil and type(opts.quiet) ~= "boolean" then + error("opts.quiet must be a boolean", 2) + end else opts = {} end @@ -270,6 +276,7 @@ function _M.new(name, shm, opts) lru_size = opts.lru_size or 100, resty_lock_opts = opts.resty_lock_opts, l1_serializer = opts.l1_serializer, + quiet = opts.quiet, } if opts.ipc_shm or opts.ipc then @@ -387,7 +394,16 @@ local function set_shm(self, shm_key, value, ttl, neg_ttl) -- nil value local ok, err = self.dict:set(shm_key, shm_nil, neg_ttl) if not ok then - return nil, "could not write to lua_shared_dict: " .. err + if err ~= "no memory" then + return nil, "could not write to lua_shared_dict '" .. self.shm + .. "': " .. err + end + + if not self.quiet then + ngx_log(WARN, "could not write to lua_shared_dict '", + self.shm, "' (no memory), it is either ", + "fragmented or cannot allocate more memory") + end end return true @@ -414,7 +430,16 @@ local function set_shm(self, shm_key, value, ttl, neg_ttl) local ok, err = self.dict:set(shm_key, shm_marshalled, ttl) if not ok then - return nil, "could not write to lua_shared_dict: " .. err + if err ~= "no memory" then + return nil, "could not write to lua_shared_dict '" .. self.shm + .. "': " .. err + end + + if not self.quiet then + ngx_log(WARN, "could not write to lua_shared_dict '", + self.shm, "' (no memory), it is either ", + "fragmented or cannot allocate more memory") + end end return true diff --git a/t/01-new.t b/t/01-new.t index ce38ac776..52207ce2f 100644 --- a/t/01-new.t +++ b/t/01-new.t @@ -472,7 +472,31 @@ opts.resty_lock_opts must be a table -=== TEST 18: new() creates an mlcache object with default attributes +=== TEST 18: new() validates opts.quiet +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local mlcache = require "resty.mlcache" + + local ok, err = pcall(mlcache.new, "name", "cache_shm", { + quiet = 123, + }) + if not ok then + ngx.log(ngx.ERR, err) + end + } + } +--- request +GET /t +--- response_body + +--- error_log +opts.quiet must be a boolean + + + +=== TEST 19: new() creates an mlcache object with default attributes --- http_config eval: $::HttpConfig --- config location = /t { @@ -500,7 +524,7 @@ number -=== TEST 19: new() accepts user-provided LRU instances via opts.lru +=== TEST 20: new() accepts user-provided LRU instances via opts.lru --- http_config eval: $::HttpConfig --- config location = /t { diff --git a/t/02-get.t b/t/02-get.t index 93f2da999..c27fd7186 100644 --- a/t/02-get.t +++ b/t/02-get.t @@ -7,7 +7,7 @@ workers(2); #repeat_each(2); -plan tests => repeat_each() * (blocks() * 3) + 5; +plan tests => repeat_each() * (blocks() * 3) + 7; my $pwd = cwd(); @@ -1648,3 +1648,170 @@ GET /t was given 'opts.resty_lock_opts': true --- no_error_log [error] + + + +=== TEST 37: 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 "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 several 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' \(no memory\), it is either/ +--- no_error_log +[error] + + + +=== TEST 38: 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 + local mlcache = require "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 several times as large + + local cache = assert(mlcache.new("my_mlcache", "cache_shm", { + ttl = 0.3, + quiet = true, + })) + + 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 39: get() + opts.quiet does not log 'no memory' warning +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local dict = ngx.shared.cache_shm + local mlcache = require "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 several times as large + + local cache = assert(mlcache.new("my_mlcache", "cache_shm", { + quiet = true, + })) + + 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 +--- no_error_log +[warn] +[error] From fbe5abb75fe14c0e27195541f9d7c50d77281cca Mon Sep 17 00:00:00 2001 From: Thibault Charbonnier Date: Sat, 17 Mar 2018 15:24:49 -0700 Subject: [PATCH 2/2] feat(get) add 'shm_set_tries' option to force LRU eviction From #41 --- README.md | 7 ++ lib/resty/mlcache.lua | 107 ++++++++++++----- t/01-new.t | 28 +++-- t/02-get.t | 260 +++++++++++++++++++++++++++++++++++++----- t/05-set.t | 78 ++++++++++++- 5 files changed, 411 insertions(+), 69 deletions(-) diff --git a/README.md b/README.md index f47233226..f1058b859 100644 --- a/README.md +++ b/README.md @@ -232,6 +232,13 @@ holding the desired options for this instance. The possible options are: cache. It can thus avoid your application from having to repeat such transformation upon every cache hit, such as creating tables, cdata objects, functions, etc... +- `shm_set_tries`: the number of tries for the lua_shared_dict `set()` + operation. When the lua_shared_dict is full, it attempts to free up to 30 + items from its queue. When the value being set is much larger than the freed + space, this option allows mlcache to retry the operation (and free more slots) + until the maximum number of tries is reached or enough memory was freed for + the value to fit. + **Default**: `3`. - `ipc_shm`: _optional_ string. If you wish to use [set()](#set), [delete()](#delete), or [purge()](#purge), you must provide an IPC (Inter-process communication) mechanism for workers to invalidate their L1 diff --git a/lib/resty/mlcache.lua b/lib/resty/mlcache.lua index 780a9ea6e..72b99ce73 100644 --- a/lib/resty/mlcache.lua +++ b/lib/resty/mlcache.lua @@ -24,6 +24,7 @@ local WARN = ngx.WARN local LOCK_KEY_PREFIX = "lua-resty-mlcache:lock:" local CACHE_MISS_SENTINEL_LRU = {} local LRU_INSTANCES = {} +local SHM_SET_DEFAULT_TRIES = 3 local c_str_type = ffi.typeof("char *") @@ -255,8 +256,14 @@ function _M.new(name, shm, opts) error("opts.l1_serializer must be a function", 2) end - if opts.quiet ~= nil and type(opts.quiet) ~= "boolean" then - error("opts.quiet must be a boolean", 2) + if opts.shm_set_tries ~= nil then + if type(opts.shm_set_tries) ~= "number" then + error("opts.shm_set_tries must be a number", 2) + end + + if opts.shm_set_tries < 1 then + error("opts.shm_set_tries must be >= 1", 2) + end end else opts = {} @@ -276,7 +283,7 @@ function _M.new(name, shm, opts) lru_size = opts.lru_size or 100, resty_lock_opts = opts.resty_lock_opts, l1_serializer = opts.l1_serializer, - quiet = opts.quiet, + shm_set_tries = opts.shm_set_tries or SHM_SET_DEFAULT_TRIES, } if opts.ipc_shm or opts.ipc then @@ -384,7 +391,42 @@ local function set_lru(self, key, value, ttl, neg_ttl, l1_serializer) end -local function set_shm(self, shm_key, value, ttl, neg_ttl) +local function shm_set_retries(self, key, val, ttl, max_tries) + -- we will call `set()` N times to work around potential shm fragmentation. + -- when the shm is full, it will only evict about 30 to 90 items (via + -- LRU), which could lead to a situation where `set()` still does not + -- have enough memory to store the cached value, in which case we + -- try again to try to trigger more LRU evictions. + + local tries = 0 + local ok, err + + while tries < max_tries do + tries = tries + 1 + + ok, err = self.dict:set(key, val, ttl) + if ok or err and err ~= "no memory" then + break + end + end + + if not ok then + if err ~= "no memory" then + return nil, "could not write to lua_shared_dict '" .. self.shm + .. "': " .. err + end + + ngx_log(WARN, "could not write to lua_shared_dict '", + self.shm, "' after ", tries, " tries (no memory), ", + "it is either fragmented or cannot allocate more ", + "memory, consider increasing 'opts.shm_set_tries'") + end + + return true +end + + +local function set_shm(self, shm_key, value, ttl, neg_ttl, shm_set_tries) local at = now() if value == nil then @@ -392,18 +434,10 @@ local function set_shm(self, shm_key, value, ttl, neg_ttl) -- we need to cache that this was a miss, and ensure cache hit for a -- nil value - local ok, err = self.dict:set(shm_key, shm_nil, neg_ttl) + local ok, err = shm_set_retries(self, shm_key, shm_nil, neg_ttl, + shm_set_tries) if not ok then - if err ~= "no memory" then - return nil, "could not write to lua_shared_dict '" .. self.shm - .. "': " .. err - end - - if not self.quiet then - ngx_log(WARN, "could not write to lua_shared_dict '", - self.shm, "' (no memory), it is either ", - "fragmented or cannot allocate more memory") - end + return nil, err end return true @@ -428,18 +462,10 @@ local function set_shm(self, shm_key, value, ttl, neg_ttl) -- cache value in shm for currently-locked workers - local ok, err = self.dict:set(shm_key, shm_marshalled, ttl) + local ok, err = shm_set_retries(self, shm_key, shm_marshalled, ttl, + shm_set_tries) if not ok then - if err ~= "no memory" then - return nil, "could not write to lua_shared_dict '" .. self.shm - .. "': " .. err - end - - if not self.quiet then - ngx_log(WARN, "could not write to lua_shared_dict '", - self.shm, "' (no memory), it is either ", - "fragmented or cannot allocate more memory") - end + return nil, err end return true @@ -487,6 +513,7 @@ local function check_opts(self, opts) local ttl local neg_ttl local l1_serializer + local shm_set_tries if opts ~= nil then if type(opts) ~= "table" then @@ -519,6 +546,17 @@ local function check_opts(self, opts) if l1_serializer ~= nil and type(l1_serializer) ~= "function" then error("opts.l1_serializer must be a function", 3) end + + shm_set_tries = opts.shm_set_tries + if shm_set_tries ~= nil then + if type(shm_set_tries) ~= "number" then + error("opts.shm_set_tries must be a number", 3) + end + + if shm_set_tries < 1 then + error("opts.shm_set_tries must be >= 1", 3) + end + end end if not ttl then @@ -533,7 +571,11 @@ local function check_opts(self, opts) l1_serializer = self.l1_serializer end - return ttl, neg_ttl, l1_serializer + if not shm_set_tries then + shm_set_tries = self.shm_set_tries + end + + return ttl, neg_ttl, l1_serializer, shm_set_tries end @@ -576,7 +618,7 @@ function _M:get(key, opts, cb, ...) -- opts validation - local ttl, neg_ttl, l1_serializer = check_opts(self, opts) + local ttl, neg_ttl, l1_serializer, shm_set_tries = check_opts(self, opts) local err data, err = get_shm_set_lru(self, key, namespaced_key, l1_serializer) @@ -647,7 +689,8 @@ function _M:get(key, opts, cb, ...) -- set shm cache level - local ok, err = set_shm(self, namespaced_key, data, ttl, neg_ttl) + local ok, err = set_shm(self, namespaced_key, data, ttl, neg_ttl, + shm_set_tries) if not ok then return unlock_and_ret(lock, nil, err) end @@ -718,12 +761,14 @@ function _M:set(key, opts, value) -- restrict this key to the current namespace, so we isolate this -- mlcache instance from potential other instances using the same -- shm - local ttl, neg_ttl, l1_serializer = check_opts(self, opts) + local ttl, neg_ttl, l1_serializer, shm_set_tries = check_opts(self, + opts) local namespaced_key = self.name .. key set_lru(self, key, value, ttl, neg_ttl, l1_serializer) - local ok, err = set_shm(self, namespaced_key, value, ttl, neg_ttl) + local ok, err = set_shm(self, namespaced_key, value, ttl, neg_ttl, + shm_set_tries) if not ok then return nil, err end diff --git a/t/01-new.t b/t/01-new.t index 52207ce2f..ec1bf2889 100644 --- a/t/01-new.t +++ b/t/01-new.t @@ -472,27 +472,37 @@ opts.resty_lock_opts must be a table -=== TEST 18: new() validates opts.quiet +=== TEST 18: new() validates opts.shm_set_tries --- http_config eval: $::HttpConfig --- config location = /t { content_by_lua_block { local mlcache = require "resty.mlcache" - local ok, err = pcall(mlcache.new, "name", "cache_shm", { - quiet = 123, - }) - if not ok then - ngx.log(ngx.ERR, err) + local values = { + false, + -1, + 0, + } + + for _, v in ipairs(values) do + local ok, err = pcall(mlcache.new, "name", "cache_shm", { + shm_set_tries = v, + }) + if not ok then + ngx.say(err) + end end } } --- request GET /t --- response_body - ---- error_log -opts.quiet must be a boolean +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] diff --git a/t/02-get.t b/t/02-get.t index c27fd7186..c5c9aee17 100644 --- a/t/02-get.t +++ b/t/02-get.t @@ -7,7 +7,7 @@ workers(2); #repeat_each(2); -plan tests => repeat_each() * (blocks() * 3) + 7; +plan tests => repeat_each() * (blocks() * 3) + 9; my $pwd = cwd(); @@ -1677,7 +1677,7 @@ was given 'opts.resty_lock_opts': true idx = idx + 1 end - -- now, trigger a hit with a value several times as large + -- now, trigger a hit with a value many times as large local cache = assert(mlcache.new("my_mlcache", "cache_shm")) @@ -1697,18 +1697,60 @@ GET /t --- response_body data type: string --- error_log eval -qr/\[warn\] .*? could not write to lua_shared_dict 'cache_shm' \(no memory\), it is either/ +qr/\[warn\] .*? could not write to lua_shared_dict 'cache_shm' after 3 tries \(no memory\), it is either/ --- no_error_log [error] -=== TEST 38: get() caches data in L1 LRU even if failed to set in shm +=== TEST 38: get() errors on invalid opts.shm_set_tries +--- http_config eval: $::HttpConfig +--- config + location = /t { + content_by_lua_block { + local mlcache = require "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 39: 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 "resty.mlcache" -- fill up shm @@ -1716,7 +1758,7 @@ qr/\[warn\] .*? could not write to lua_shared_dict 'cache_shm' \(no memory\), it local idx = 0 while true do - local ok, err, forcible = dict:set(idx, string.rep("a", 2^5)) + local ok, err, forcible = dict:set(idx, string.rep("a", 2^2)) if not ok then ngx.log(ngx.ERR, err) return @@ -1729,48 +1771,130 @@ qr/\[warn\] .*? could not write to lua_shared_dict 'cache_shm' \(no memory\), it idx = idx + 1 end - -- now, trigger a hit with a value several times as large + -- 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 40: 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 "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", { - ttl = 0.3, - quiet = true, + shm_set_tries = 5 })) - local data, err = cache:get("key", nil, function() - return string.rep("a", 2^20) - end) + 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 - local data = cache.lru:get("key") - ngx.say("type of data in LRU: ", type(data)) + -- from shm - ngx.say("sleeping...") - ngx.sleep(0.4) + cache.lru:delete("key") - local _, stale = cache.lru:get("key") - ngx.say("is stale: ", stale ~= nil) + 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 LRU: string -sleeping... -is stale: true +type of data in shm: string +callback was called: 1 times --- no_error_log +[warn] [error] -=== TEST 39: get() + opts.quiet does not log 'no memory' warning +=== TEST 41: 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 "resty.mlcache" -- fill up shm @@ -1778,7 +1902,7 @@ is stale: true local idx = 0 while true do - local ok, err, forcible = dict:set(idx, string.rep("a", 2^5)) + local ok, err, forcible = dict:set(idx, string.rep("a", 2^2)) if not ok then ngx.log(ngx.ERR, err) return @@ -1791,10 +1915,84 @@ is stale: true idx = idx + 1 end - -- now, trigger a hit with a value several times as large + -- 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 42: 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 "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", { - quiet = true, + ttl = 0.3, + shm_set_tries = 1, })) local data, err = cache:get("key", nil, function() @@ -1805,13 +2003,21 @@ is stale: true return end - ngx.say("data type: ", type(data)) + 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 -data type: string +type of data in LRU: string +sleeping... +is stale: true --- no_error_log -[warn] [error] diff --git a/t/05-set.t b/t/05-set.t index efa3f741b..37e666b3a 100644 --- a/t/05-set.t +++ b/t/05-set.t @@ -5,7 +5,7 @@ use Cwd qw(cwd); #repeat_each(2); -plan tests => repeat_each() * (blocks() * 3); +plan tests => repeat_each() * (blocks() * 3) + 1; my $pwd = cwd(); @@ -266,7 +266,81 @@ value from get(): nil -=== TEST 7: set() calls broadcast() with invalidated key +=== TEST 7: 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 "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 8: set() calls broadcast() with invalidated key --- http_config eval: $::HttpConfig --- config location = /t {