This is an automated email from the ASF dual-hosted git repository.
nic443 pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/apisix.git
The following commit(s) were added to refs/heads/master by this push:
new 04bb011a4 fix(secret): refresh stale lru cache item in background
(#12614)
04bb011a4 is described below
commit 04bb011a4073370278324b6eb277d42d888d0279
Author: Nic <[email protected]>
AuthorDate: Tue Sep 16 20:36:53 2025 +0800
fix(secret): refresh stale lru cache item in background (#12614)
Signed-off-by: Nic <[email protected]>
---
apisix/cli/config.lua | 6 ++++
apisix/core/lrucache.lua | 83 ++++++++++++++++++++++++++++++++++++++---------
apisix/init.lua | 4 +++
apisix/secret.lua | 21 ++++++++++--
conf/config.yaml.example | 5 +++
t/core/lrucache.t | 40 ++---------------------
t/router/radixtree-sni2.t | 64 +++++++++++++++++++++++++++++++++---
7 files changed, 164 insertions(+), 59 deletions(-)
diff --git a/apisix/cli/config.lua b/apisix/cli/config.lua
index 2c57541ba..0b9a23198 100644
--- a/apisix/cli/config.lua
+++ b/apisix/cli/config.lua
@@ -79,6 +79,12 @@ local _M = {
},
events = {
module = "lua-resty-events"
+ },
+ lru = {
+ secret = {
+ ttl = 300,
+ count = 512
+ }
}
},
nginx_config = {
diff --git a/apisix/core/lrucache.lua b/apisix/core/lrucache.lua
index 5c81dd317..374d33c90 100644
--- a/apisix/core/lrucache.lua
+++ b/apisix/core/lrucache.lua
@@ -22,9 +22,14 @@
local lru_new = require("resty.lrucache").new
local resty_lock = require("resty.lock")
local log = require("apisix.core.log")
+local pairs = pairs
+local pcall = pcall
+local unpack = unpack
local tostring = tostring
local ngx = ngx
local get_phase = ngx.get_phase
+local timer_every = ngx.timer.every
+local exiting = ngx.worker.exiting
local lock_shdict_name = "lrucache-lock"
@@ -42,6 +47,8 @@ local can_yield_phases = {
timer = true
}
+local stale_obj_pool = {}
+
local GLOBAL_ITEMS_COUNT = 1024
local GLOBAL_TTL = 60 * 60 -- 60 min
local PLUGIN_TTL = 5 * 60 -- 5 min
@@ -49,20 +56,28 @@ local PLUGIN_ITEMS_COUNT = 8
local global_lru_fun
-local function fetch_valid_cache(lru_obj, invalid_stale, item_ttl,
- item_release, key, version)
+local function fetch_valid_cache(lru_obj, invalid_stale, refresh_stale,
item_ttl,
+ key, version, create_obj_fun, ...)
local obj, stale_obj = lru_obj:get(key)
if obj and obj.ver == version then
return obj
end
- if not invalid_stale and stale_obj and stale_obj.ver == version then
- lru_obj:set(key, stale_obj, item_ttl)
- return stale_obj
- end
+ if stale_obj and stale_obj.ver == version then
+ if not invalid_stale then
+ lru_obj:set(key, stale_obj, item_ttl)
+ return stale_obj
+ end
- if item_release and obj then
- item_release(obj.val)
+ if refresh_stale then
+ stale_obj_pool[lru_obj][key] = {
+ fn = create_obj_fun,
+ args = {...},
+ ver = version,
+ ttl = item_ttl,
+ }
+ return stale_obj
+ end
end
return nil
@@ -79,15 +94,16 @@ local function new_lru_fun(opts)
item_ttl = opts and opts.ttl or GLOBAL_TTL
end
- local item_release = opts and opts.release
local invalid_stale = opts and opts.invalid_stale
+ local refresh_stale = opts and opts.refresh_stale
local serial_creating = opts and opts.serial_creating
local lru_obj = lru_new(item_count)
+ stale_obj_pool[lru_obj] = {}
return function (key, version, create_obj_fun, ...)
if not serial_creating or not can_yield_phases[get_phase()] then
- local cache_obj = fetch_valid_cache(lru_obj, invalid_stale,
- item_ttl, item_release, key, version)
+ local cache_obj = fetch_valid_cache(lru_obj, invalid_stale,
refresh_stale,
+ item_ttl, key, version, create_obj_fun, ...)
if cache_obj then
return cache_obj.val
end
@@ -100,8 +116,8 @@ local function new_lru_fun(opts)
return obj, err
end
- local cache_obj = fetch_valid_cache(lru_obj, invalid_stale, item_ttl,
- item_release, key, version)
+ local cache_obj = fetch_valid_cache(lru_obj, invalid_stale,
refresh_stale, item_ttl,
+ key, version, create_obj_fun, ...)
if cache_obj then
return cache_obj.val
end
@@ -119,8 +135,8 @@ local function new_lru_fun(opts)
return nil, "failed to acquire the lock: " .. err
end
- cache_obj = fetch_valid_cache(lru_obj, invalid_stale, item_ttl,
- nil, key, version)
+ cache_obj = fetch_valid_cache(lru_obj, invalid_stale, refresh_stale,
item_ttl,
+ key, version, create_obj_fun, ...)
if cache_obj then
lock:unlock()
log.info("unlock with key ", key_s)
@@ -181,8 +197,45 @@ local function plugin_ctx_id(api_ctx, extra_key)
end
+local function refresh_stale_objs()
+ for lru_obj, keys in pairs(stale_obj_pool) do
+ for key, new_obj in pairs(keys) do
+ local obj, err = new_obj.fn(unpack(new_obj.args))
+ if obj ~= nil then
+ lru_obj:set(key, {val = obj, ver = new_obj.ver}, new_obj.ttl)
+ keys[key] = nil
+ log.info("successfully refresh stale obj for key ",
+ tostring(key), " to ver ", new_obj.ver)
+ else
+ log.error("failed to refresh stale obj for key ", key, ": ",
err)
+ end
+ end
+ end
+end
+
+
+local function init_worker()
+ local running = false
+ timer_every(1, function ()
+ if not exiting() then
+ if running then
+ log.info("timer_refresh_stale is already running, skipping
this iteration")
+ return
+ end
+ running = true
+ local ok, err = pcall(refresh_stale_objs)
+ if not ok then
+ log.error("failed to run timer_refresh_stale: ", err)
+ end
+ running = false
+ end
+ end)
+end
+
+
local _M = {
version = 0.1,
+ init_worker = init_worker,
new = new_lru_fun,
global = global_lru_fun,
plugin_ctx = plugin_ctx,
diff --git a/apisix/init.lua b/apisix/init.lua
index 5af93d142..d6f7a1a3d 100644
--- a/apisix/init.lua
+++ b/apisix/init.lua
@@ -117,6 +117,8 @@ function _M.http_init_worker()
require("apisix.events").init_worker()
+ core.lrucache.init_worker()
+
local discovery = require("apisix.discovery.init").discovery
if discovery and discovery.init_worker then
discovery.init_worker()
@@ -1088,6 +1090,8 @@ function _M.stream_init_worker()
-- for testing only
core.log.info("random stream test in [1, 10000]: ", math.random(1, 10000))
+ core.lrucache.init_worker()
+
if core.config.init_worker then
local ok, err = core.config.init_worker()
if not ok then
diff --git a/apisix/secret.lua b/apisix/secret.lua
index 60e575b92..b8d7b19a5 100644
--- a/apisix/secret.lua
+++ b/apisix/secret.lua
@@ -19,6 +19,8 @@ local require = require
local core = require("apisix.core")
local string = require("apisix.core.string")
+local local_conf = require("apisix.core.config_local").local_conf()
+
local find = string.find
local sub = string.sub
local upper = string.upper
@@ -185,9 +187,22 @@ local function fetch(uri)
end
-local secrets_lrucache = core.lrucache.new({
- ttl = 300, count = 512
-})
+local function new_lrucache()
+ local ttl = core.table.try_read_attr(local_conf, "apisix", "lru",
"secret", "ttl")
+ if not ttl then
+ ttl = 300
+ end
+ local count = core.table.try_read_attr(local_conf, "apisix", "lru",
"secret", "count")
+ if not count then
+ count = 512
+ end
+ core.log.info("secret lrucache ttl: ", ttl, ", count: ", count)
+ return core.lrucache.new({
+ ttl = ttl, count = count, invalid_stale = true, refresh_stale = true
+ })
+end
+local secrets_lrucache = new_lrucache()
+
local fetch_secrets
do
diff --git a/conf/config.yaml.example b/conf/config.yaml.example
index 408fb5138..7a0f6939c 100644
--- a/conf/config.yaml.example
+++ b/conf/config.yaml.example
@@ -141,6 +141,11 @@ apisix:
# or (standalone mode) the config isn't
loaded yet either via file or Admin API.
# disable_upstream_healthcheck: false # A global switch for healthcheck.
Defaults to false.
# When set to true, it overrides all
upstream healthcheck configurations and globally disabling healthchecks.
+ # fine tune the parameters of LRU cache for some features like secret
+ lru:
+ secret:
+ ttl: 300 # seconds
+ count: 512
nginx_config: # Config for render the template to generate
nginx.conf
# user: root # Set the execution user of the worker
process. This is only
# effective if the master process runs with
super-user privileges.
diff --git a/t/core/lrucache.t b/t/core/lrucache.t
index 0b75e8e61..905c20fd0 100644
--- a/t/core/lrucache.t
+++ b/t/core/lrucache.t
@@ -128,41 +128,7 @@ obj: 2
-=== TEST 4: sanity
---- config
- location /t {
- content_by_lua_block {
- local core = require("apisix.core")
-
- local function server_release(self)
- ngx.say("release: ", require("toolkit.json").encode(self))
- end
-
- local lrucache_server_picker = core.lrucache.new({
- ttl = 300, count = 256, release = server_release,
- })
-
- local t1 = lrucache_server_picker("nnn", "t1",
- function () return {name = "aaa"} end)
-
- ngx.say("obj: ", require("toolkit.json").encode(t1))
-
- local t2 = lrucache_server_picker("nnn", "t2",
- function () return {name = "bbb"} end)
-
- ngx.say("obj: ", require("toolkit.json").encode(t2))
- }
- }
---- request
-GET /t
---- response_body
-obj: {"name":"aaa"}
-release: {"name":"aaa"}
-obj: {"name":"bbb"}
-
-
-
-=== TEST 5: invalid_stale = true
+=== TEST 4: invalid_stale = true
--- config
location /t {
content_by_lua_block {
@@ -197,7 +163,7 @@ obj: {"idx":2}
-=== TEST 6: when creating cached objects, use resty-lock to avoid repeated
creation.
+=== TEST 5: when creating cached objects, use resty-lock to avoid repeated
creation.
--- config
location /t {
content_by_lua_block {
@@ -233,7 +199,7 @@ obj: {"idx":1}
-=== TEST 7: different `key` and `ver`, cached same one table
+=== TEST 6: different `key` and `ver`, cached same one table
--- config
location /t {
content_by_lua_block {
diff --git a/t/router/radixtree-sni2.t b/t/router/radixtree-sni2.t
index c761c9043..79d23df8e 100644
--- a/t/router/radixtree-sni2.t
+++ b/t/router/radixtree-sni2.t
@@ -697,7 +697,63 @@ ssl handshake: true
-=== TEST 18: set ssl conf with secret ref: env
+=== TEST 18: get cert and key from vault with a custom ttl
+--- yaml_config
+apisix:
+ lru:
+ secret:
+ ttl: 2
+ count: 1024
+--- config
+listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
+location /t {
+ content_by_lua_block {
+ local tls_handshake = function()
+ local sock = ngx.socket.tcp()
+ sock:settimeout(2000)
+ local ok, err =
sock:connect("unix:$TEST_NGINX_HTML_DIR/nginx.sock")
+ if not ok then
+ ngx.say("failed to connect: ", err)
+ return
+ end
+ local sess, err = sock:sslhandshake(nil, "test2.com", false)
+ if not sess then
+ ngx.say("failed to do SSL handshake: ", err)
+ return
+ end
+ sock:close()
+ end
+ -- send three requests within ttl
+ for i = 1, 3 do
+ tls_handshake()
+ end
+ -- wait for ttl to expire
+ ngx.sleep(2)
+ -- send one requests to trigger refresh
+ tls_handshake()
+ -- wait for refresh to complete
+ ngx.sleep(1)
+ -- send another three requests after refresh
+ for i = 1, 3 do
+ tls_handshake()
+ end
+ ngx.say("passed")
+ }
+}
+--- response_body
+passed
+--- error_log
+secret lrucache ttl: 2, count: 1024
+successfully refresh stale obj for key
+--- grep_error_log eval
+qr/fetching data from secret uri:
\$secret:\/\/vault\/test1\/ssl\/test2.com.crt, context: \S+/
+--- grep_error_log_out
+fetching data from secret uri: $secret://vault/test1/ssl/test2.com.crt,
context: ssl_certificate_by_lua*,
+fetching data from secret uri: $secret://vault/test1/ssl/test2.com.crt,
context: ngx.timer
+
+
+
+=== TEST 19: set ssl conf with secret ref: env
--- request
GET /t
--- config
@@ -726,7 +782,7 @@ passed
-=== TEST 19: get cert and key from env
+=== TEST 20: get cert and key from env
--- config
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;
@@ -758,7 +814,7 @@ ssl handshake: true
-=== TEST 20: set ssl conf with secret ref: only cert use env
+=== TEST 21: set ssl conf with secret ref: only cert use env
--- request
GET /t
--- config
@@ -791,7 +847,7 @@ passed
-=== TEST 21: get cert from env
+=== TEST 22: get cert from env
--- config
listen unix:$TEST_NGINX_HTML_DIR/nginx.sock ssl;