This is an automated email from the ASF dual-hosted git repository. nic-6443 pushed a commit to branch fix/module-level-mutable-tables in repository https://gitbox.apache.org/repos/asf/apisix.git
commit 69e57224aa1147d3f5faab88c584956e00032b58 Author: Nic <[email protected]> AuthorDate: Wed May 13 18:33:30 2026 +0800 fix: replace module-level mutable tables with per-call allocation Several plugins and routers declared `local tbl = {}` at module scope and reused the same table across coroutines via `core.table.clear()`. When a function yields (e.g. `ctx.var[...]` lookup or shdict op), another concurrent request on the same worker can re-enter and mutate the shared table, causing cross-request state pollution. Affected high-risk sites (yield in function body): - prometheus/exporter.lua: inner_tab_arr in gen_arr(), extra_labels_tbl in extra_labels() — use per-call local table - proxy-cache/util.lua: tmp in generate_complex_value() — use tablepool - redirect.lua: tmp in concat_new_uri() — use tablepool Affected low-risk sites (preventive, no current yield): - api_router.lua: match_opts in match() — use tablepool - control/router.lua: match_opts in match() — use tablepool - stream/router/ip_port.lua: match_opts in match() — use tablepool This follows the same fix pattern as the historical radixtree_host_uri route-mismatch fix (PR #10198). --- apisix/api_router.lua | 4 ++-- apisix/control/router.lua | 7 ++++--- apisix/plugins/prometheus/exporter.lua | 19 ++++++++----------- apisix/plugins/proxy-cache/util.lua | 7 ++++--- apisix/plugins/redirect.lua | 7 ++++--- apisix/stream/router/ip_port.lua | 5 ++--- 6 files changed, 24 insertions(+), 25 deletions(-) diff --git a/apisix/api_router.lua b/apisix/api_router.lua index 9fbf328bf..92b210d3e 100644 --- a/apisix/api_router.lua +++ b/apisix/api_router.lua @@ -24,7 +24,6 @@ local type = type local _M = {} -local match_opts = {} local has_route_not_under_apisix @@ -105,10 +104,11 @@ function _M.match(api_ctx) return false end - core.table.clear(match_opts) + local match_opts = core.tablepool.fetch("api_router_match_opts", 0, 4) match_opts.method = api_ctx.var.request_method local ok = api_router:dispatch(api_ctx.var.uri, match_opts, api_ctx) + core.tablepool.release("api_router_match_opts", match_opts) return ok end diff --git a/apisix/control/router.lua b/apisix/control/router.lua index e6e5ff9b3..57eb7edd6 100644 --- a/apisix/control/router.lua +++ b/apisix/control/router.lua @@ -174,7 +174,6 @@ end -- do do - local match_opts = {} local cached_version local router @@ -190,10 +189,12 @@ function _M.match(uri) cached_version = plugin_mod.load_times end - core.table.clear(match_opts) + local match_opts = core.tablepool.fetch("control_router_match_opts", 0, 4) match_opts.method = get_method() - return router:dispatch(uri, match_opts) + local ok = router:dispatch(uri, match_opts) + core.tablepool.release("control_router_match_opts", match_opts) + return ok end end -- do diff --git a/apisix/plugins/prometheus/exporter.lua b/apisix/plugins/prometheus/exporter.lua index ce89ca033..7095ae194 100644 --- a/apisix/plugins/prometheus/exporter.lua +++ b/apisix/plugins/prometheus/exporter.lua @@ -65,26 +65,23 @@ local CACHED_METRICS_KEY = "cached_metrics_text" local metrics = {} -local inner_tab_arr = {} - local exporter_timer_running = false local exporter_timer_created = false local function gen_arr(...) - clear_tab(inner_tab_arr) - for i = 1, select('#', ...) do - inner_tab_arr[i] = select(i, ...) + local n = select('#', ...) + local arr = {} + for i = 1, n do + arr[i] = select(i, ...) end - return inner_tab_arr + return arr end -local extra_labels_tbl = {} - local function extra_labels(name, ctx) - clear_tab(extra_labels_tbl) + local tbl = {} local attr = plugin.plugin_attr("prometheus") local metrics = attr.metrics @@ -99,11 +96,11 @@ local function extra_labels(name, ctx) val = "" end end - core.table.insert(extra_labels_tbl, val) + core.table.insert(tbl, val) end end - return extra_labels_tbl + return tbl end diff --git a/apisix/plugins/proxy-cache/util.lua b/apisix/plugins/proxy-cache/util.lua index 26c6e814b..4e7266323 100644 --- a/apisix/plugins/proxy-cache/util.lua +++ b/apisix/plugins/proxy-cache/util.lua @@ -28,9 +28,8 @@ local tonumber = tonumber local _M = {} -local tmp = {} function _M.generate_complex_value(data, ctx) - core.table.clear(tmp) + local tmp = core.tablepool.fetch("proxy_cache_complex_value", #data, 0) core.log.info("proxy-cache complex value: ", core.json.delay_encode(data)) for i, value in ipairs(data) do @@ -43,7 +42,9 @@ function _M.generate_complex_value(data, ctx) end end - return tab_concat(tmp, "") + local result = tab_concat(tmp, "") + core.tablepool.release("proxy_cache_complex_value", tmp) + return result end diff --git a/apisix/plugins/redirect.lua b/apisix/plugins/redirect.lua index da81859c3..b07754a17 100644 --- a/apisix/plugins/redirect.lua +++ b/apisix/plugins/redirect.lua @@ -131,14 +131,13 @@ function _M.check_schema(conf) end - local tmp = {} local function concat_new_uri(uri, ctx) local passed_uri_segs, err = lrucache(uri, nil, parse_uri, uri) if not passed_uri_segs then return nil, err end - core.table.clear(tmp) + local tmp = core.tablepool.fetch("redirect_new_uri", #passed_uri_segs, 0) for _, uri_segs in ipairs(passed_uri_segs) do local pat1 = uri_segs[1] -- \$host @@ -154,7 +153,9 @@ local function concat_new_uri(uri, ctx) end end - return tab_concat(tmp, "") + local result = tab_concat(tmp, "") + core.tablepool.release("redirect_new_uri", tmp) + return result end local function get_port(attr) diff --git a/apisix/stream/router/ip_port.lua b/apisix/stream/router/ip_port.lua index 3b89b59a2..45e8e5531 100644 --- a/apisix/stream/router/ip_port.lua +++ b/apisix/stream/router/ip_port.lua @@ -144,8 +144,6 @@ end do - local match_opts = {} - function _M.match(api_ctx) -- Rebuild the router when stream_routes change OR when services change, -- so updates to a referenced service (status, deletion, late sync from @@ -166,10 +164,11 @@ do if sni and tls_router then local sni_rev = sni:reverse() - core.table.clear(match_opts) + local match_opts = core.tablepool.fetch("stream_router_match_opts", 0, 4) match_opts.vars = api_ctx.var local _, err = tls_router:dispatch(sni_rev, match_opts, api_ctx) + core.tablepool.release("stream_router_match_opts", match_opts) if err then return false, "failed to match TLS router: " .. err end
