diff --git a/README.markdown b/README.markdown index 917e635..5066d68 100644 --- a/README.markdown +++ b/README.markdown @@ -14,6 +14,7 @@ Table of Contents * [new](#new) * [set](#set) * [get](#get) + * [ttl](#ttl) * [delete](#delete) * [count](#count) * [capacity](#capacity) @@ -206,6 +207,22 @@ item, its default flags will be `0`. [Back to TOC](#table-of-contents) +ttl +--- +`syntax: ttl = cache:ttl(key, update_queue?)` + +Retrieves the remaining TTL (time-to-live in seconds) of a key. If the key does +not exist in the cache or has already expired, `nil` will be returned. If the +key does not have a TTL, `-1` will be returned. + +The optional boolean `update_queue` argument specifies if the cache entry will +be put on top of the internal LRU queue (like when calling [cache:set](#set) or +[cache:get](#get)) as part of the `cache:ttl` call. + +This feature was first introduced in the v0.16 release. + +[Back to TOC](#table-of-contents) + delete ------ `syntax: cache:delete(key)` diff --git a/lib/resty/lrucache.lua b/lib/resty/lrucache.lua index 20f27a9..a6bee96 100644 --- a/lib/resty/lrucache.lua +++ b/lib/resty/lrucache.lua @@ -204,6 +204,37 @@ function _M.get(self, key) end +function _M.ttl(self, key, update_queue) + local hasht = self.hasht + local val = hasht[key] + if val == nil then + return nil + end + + local node = self.key2node[key] + + if update_queue then + -- print(key, ": moving node ", tostring(node), " to cache queue head") + local cache_queue = self.cache_queue + queue_remove(node) + queue_insert_head(cache_queue, node) + end + + local now = ngx_now() + local expire = node.expire + if expire >= 0 then + if expire < now then + -- print("expired: ", expire, " > ", ngx_now()) + return nil + else + return expire - now + end + end + + return -1 +end + + function _M.delete(self, key) self.hasht[key] = nil diff --git a/lib/resty/lrucache/pureffi.lua b/lib/resty/lrucache/pureffi.lua index 289b183..e54e78a 100644 --- a/lib/resty/lrucache/pureffi.lua +++ b/lib/resty/lrucache/pureffi.lua @@ -496,6 +496,39 @@ function _M.get(self, key) end +function _M.ttl(self, key, update_queue) + if type(key) ~= "string" then + key = tostring(key) + end + + local node_id = find_key(self, key) + if not node_id then + return nil + end + + local node = self.node_v + node_id + if update_queue then + -- print(key, ": moving node ", tostring(node), " to cache queue head") + local cache_queue = self.cache_queue + queue_remove(node) + queue_insert_head(cache_queue, node) + end + + local now = ngx_now() + local expire = node.expire + if expire >= 0 then + if expire < now then + -- print("expired: ", expire, " > ", ngx_now()) + return nil + else + return expire - now + end + end + + return -1 +end + + function _M.delete(self, key) if type(key) ~= "string" then key = tostring(key) diff --git a/t/009-ttl.t b/t/009-ttl.t new file mode 100644 index 0000000..c75827d --- /dev/null +++ b/t/009-ttl.t @@ -0,0 +1,133 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestLRUCache; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: no ttl +--- config + location = /t { + content_by_lua ' + local lrucache = require "resty.lrucache" + local c = lrucache.new(2) + if not c then + ngx.say("failed to init lrucache: ", err) + return + end + + c:set("dog", 32) + + ngx.say("ttl: ", (c:ttl("dog"))) + '; + } +--- response_body +ttl: -1 + + + +=== TEST 2: ttl defined +--- config + location = /t { + content_by_lua ' + local lrucache = require "resty.lrucache" + local c = lrucache.new(2) + if not c then + ngx.say("failed to init lrucache: ", err) + return + end + + c:set("dog", 32, 0.2) + ngx.say("ttl: ", ((c:ttl("dog") or 0) > 0)) + + ngx.sleep(0.3) + + ngx.say("ttl: ", ((c:ttl("dog") or 0) > 0)) + '; + } +--- response_body +ttl: true +ttl: false + + + +=== TEST 3: update_queue is false +--- config + location = /t { + content_by_lua ' + local lrucache = require "resty.lrucache" + local c = lrucache.new(2) + if not c then + ngx.say("failed to init lrucache: ", err) + return + end + + c:set("dog", 32) + c:set("cat", 33) + + ngx.say("dog: ", (c:get("dog"))) + ngx.say("cat: ", (c:get("cat"))) + + ngx.say("ttl cat: ", (c:ttl("cat"))) + ngx.say("ttl dog: ", (c:ttl("dog"))) + + c:set("bird", 76) + + ngx.say("dog: ", (c:get("dog"))) + ngx.say("cat: ", (c:get("cat"))) + ngx.say("bird: ", (c:get("bird"))) + '; + } +--- response_body +dog: 32 +cat: 33 +ttl cat: -1 +ttl dog: -1 +dog: nil +cat: 33 +bird: 76 + + + +=== TEST 4: update_queue is true +--- config + location = /t { + content_by_lua ' + local lrucache = require "resty.lrucache" + local c = lrucache.new(2) + if not c then + ngx.say("failed to init lrucache: ", err) + return + end + + c:set("dog", 32) + c:set("cat", 33) + + ngx.say("dog: ", (c:get("dog"))) + ngx.say("cat: ", (c:get("cat"))) + + ngx.say("ttl cat: ", (c:ttl("cat", true))) + ngx.say("ttl dog: ", (c:ttl("dog", true))) + + c:set("bird", 76) + + ngx.say("dog: ", (c:get("dog"))) + ngx.say("cat: ", (c:get("cat"))) + ngx.say("bird: ", (c:get("bird"))) + '; + } +--- response_body +dog: 32 +cat: 33 +ttl cat: -1 +ttl dog: -1 +dog: 32 +cat: nil +bird: 76 + diff --git a/t/100-pureffi/009-ttl.t b/t/100-pureffi/009-ttl.t new file mode 100644 index 0000000..e65bb53 --- /dev/null +++ b/t/100-pureffi/009-ttl.t @@ -0,0 +1,133 @@ +# vim:set ft= ts=4 sw=4 et fdm=marker: +use lib '.'; +use t::TestLRUCache; + +repeat_each(2); + +plan tests => repeat_each() * (blocks() * 3); + +no_long_string(); +run_tests(); + +__DATA__ + +=== TEST 1: no ttl +--- config + location = /t { + content_by_lua ' + local lrucache = require "resty.lrucache.pureffi" + local c = lrucache.new(2) + if not c then + ngx.say("failed to init lrucache: ", err) + return + end + + c:set("dog", 32) + + ngx.say("ttl: ", (c:ttl("dog"))) + '; + } +--- response_body +ttl: -1 + + + +=== TEST 2: ttl defined +--- config + location = /t { + content_by_lua ' + local lrucache = require "resty.lrucache.pureffi" + local c = lrucache.new(2) + if not c then + ngx.say("failed to init lrucache: ", err) + return + end + + c:set("dog", 32, 0.2) + ngx.say("ttl: ", ((c:ttl("dog") or 0) > 0)) + + ngx.sleep(0.3) + + ngx.say("ttl: ", ((c:ttl("dog") or 0) > 0)) + '; + } +--- response_body +ttl: true +ttl: false + + + +=== TEST 3: update_queue is false +--- config + location = /t { + content_by_lua ' + local lrucache = require "resty.lrucache.pureffi" + local c = lrucache.new(2) + if not c then + ngx.say("failed to init lrucache: ", err) + return + end + + c:set("dog", 32) + c:set("cat", 33) + + ngx.say("dog: ", (c:get("dog"))) + ngx.say("cat: ", (c:get("cat"))) + + ngx.say("ttl cat: ", (c:ttl("cat"))) + ngx.say("ttl dog: ", (c:ttl("dog"))) + + c:set("bird", 76) + + ngx.say("dog: ", (c:get("dog"))) + ngx.say("cat: ", (c:get("cat"))) + ngx.say("bird: ", (c:get("bird"))) + '; + } +--- response_body +dog: 32 +cat: 33 +ttl cat: -1 +ttl dog: -1 +dog: nil +cat: 33 +bird: 76 + + + +=== TEST 4: update_queue is true +--- config + location = /t { + content_by_lua ' + local lrucache = require "resty.lrucache.pureffi" + local c = lrucache.new(2) + if not c then + ngx.say("failed to init lrucache: ", err) + return + end + + c:set("dog", 32) + c:set("cat", 33) + + ngx.say("dog: ", (c:get("dog"))) + ngx.say("cat: ", (c:get("cat"))) + + ngx.say("ttl cat: ", (c:ttl("cat", true))) + ngx.say("ttl dog: ", (c:ttl("dog", true))) + + c:set("bird", 76) + + ngx.say("dog: ", (c:get("dog"))) + ngx.say("cat: ", (c:get("cat"))) + ngx.say("bird: ", (c:get("bird"))) + '; + } +--- response_body +dog: 32 +cat: 33 +ttl cat: -1 +ttl dog: -1 +dog: 32 +cat: nil +bird: 76 +