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

Reply via email to