This is an automated email from the ASF dual-hosted git repository.

AlinsRan 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 08aef845b fix(plugin): preserve plugin-conf state across consumer 
route merge (#13757)
08aef845b is described below

commit 08aef845bda644ce9a19f2ef465dbc40b6ca233c
Author: AlinsRan <[email protected]>
AuthorDate: Wed Aug 5 06:35:33 2026 +0800

    fix(plugin): preserve plugin-conf state across consumer route merge (#13757)
---
 apisix/core/table.lua | 19 +++++++++++++++-
 apisix/plugin.lua     |  9 +++++++-
 t/core/table.t        | 62 +++++++++++++++++++++++++++++++++++++++++++++++++++
 3 files changed, 88 insertions(+), 2 deletions(-)

diff --git a/apisix/core/table.lua b/apisix/core/table.lua
index 60b0a6eba..97c37f295 100644
--- a/apisix/core/table.lua
+++ b/apisix/core/table.lua
@@ -31,6 +31,9 @@ local pairs        = pairs
 local type         = type
 local ngx_re       = require("ngx.re")
 local isarray      = require("table.isarray")
+local str_byte     = string.byte
+local str_has_prefix = require("apisix.core.string").has_prefix
+local DOT          = str_byte(".")
 
 
 local _M = {
@@ -120,6 +123,18 @@ end
 
 local deepcopy
 do
+    -- the members of `parent` are shallow-copied when `parent` is `prefix`
+    -- itself or one of its descendants. The match has to stop at a path
+    -- separator, otherwise a prefix like "self.value.plugins" would also
+    -- claim a sibling named "self.value.plugins2".
+    local function under_shallow_prefix(parent, prefix)
+        if not str_has_prefix(parent, prefix) then
+            return false
+        end
+
+        return #parent == #prefix or str_byte(parent, #prefix + 1) == DOT
+    end
+
     local function _deepcopy(orig, copied, parent, opts)
         -- If the array-like table contains nil in the middle,
         -- the len might be smaller than the expected.
@@ -130,7 +145,9 @@ do
         copied[orig] = copy
         for orig_key, orig_value in pairs(orig) do
             local path = parent .. "." .. tostring(orig_key)
-            if opts and array_find(opts.shallows, path) then
+            if opts and (array_find(opts.shallows, path) or
+                (opts.shallow_prefix and
+                 under_shallow_prefix(parent, opts.shallow_prefix))) then
                 copy[orig_key] = orig_value
             else
                 if type(orig_value) == "table" then
diff --git a/apisix/plugin.lua b/apisix/plugin.lua
index 7a0159fd9..d00cc5310 100644
--- a/apisix/plugin.lua
+++ b/apisix/plugin.lua
@@ -818,7 +818,14 @@ local function merge_consumer_route(route_conf, 
consumer_conf, consumer_group_co
         return route_conf
     end
 
-    local new_route_conf = core.table.deepcopy(route_conf)
+    -- some plugins cache request-time state on their conf object (resolved DNS
+    -- nodes, probed backend versions, ...). Deep-copying the plugin confs 
would
+    -- hand every consumer its own copy and drop that state, so keep them 
shared
+    -- by reference, as they already are on the route without consumer auth. 
The
+    -- `plugins` container itself is still a fresh table, so the merge below
+    -- overwrites its keys without touching the original route conf.
+    local new_route_conf = core.table.deepcopy(route_conf,
+        { shallow_prefix = "self.value.plugins" })
 
     if has_group_plugins then
         for name, conf in pairs(consumer_group_conf.value.plugins) do
diff --git a/t/core/table.t b/t/core/table.t
index 38616ae53..e1e4d6cfa 100644
--- a/t/core/table.t
+++ b/t/core/table.t
@@ -359,3 +359,65 @@ tab_copied.a.b.c == tab.a.b.c1: true
 tab_copied.a.b.c == t1: true
 tab_copied.x.y == tab.x.y: false
 tab_copied.x.y == t2: false
+
+
+
+=== TEST 13: shallow_prefix
+--- config
+    location /t {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local deepcopy = core.table.deepcopy
+            local plugin1 = {name = "plugin1"}
+            local plugin2 = {name = "plugin2"}
+            local tab = {
+                plugins = {
+                    p1 = plugin1,
+                    p2 = plugin2,
+                },
+            }
+
+            local tab_copied = deepcopy(tab, { shallow_prefix = "self.plugins" 
})
+
+            tab_copied.plugins.p1 = {name = "plugin1_new_modified"}
+            tab_copied.plugins.p2.name = "plugin2_modified"
+
+            ngx.say("table copied: ", 
require("toolkit.json").encode(tab_copied))
+            ngx.say("table original: ", require("toolkit.json").encode(tab))
+        }
+    }
+--- request
+GET /t
+--- response_body
+table copied: 
{"plugins":{"p1":{"name":"plugin1_new_modified"},"p2":{"name":"plugin2_modified"}}}
+table original: 
{"plugins":{"p1":{"name":"plugin1"},"p2":{"name":"plugin2_modified"}}}
+--- no_error_log
+[error]
+
+
+
+=== TEST 14: shallow_prefix only matches at a path separator
+--- config
+    location /t {
+        content_by_lua_block {
+            local core = require("apisix.core")
+            local deepcopy = core.table.deepcopy
+            local tab = {
+                plugins = { p1 = {name = "p1"} },
+                -- a sibling whose name starts with the prefix
+                plugins2 = { p2 = {name = "p2"} },
+            }
+
+            local tab_copied = deepcopy(tab, { shallow_prefix = "self.plugins" 
})
+
+            ngx.say("plugins.p1 shared: ", tab_copied.plugins.p1 == 
tab.plugins.p1)
+            ngx.say("plugins2.p2 shared: ", tab_copied.plugins2.p2 == 
tab.plugins2.p2)
+        }
+    }
+--- request
+GET /t
+--- response_body
+plugins.p1 shared: true
+plugins2.p2 shared: false
+--- no_error_log
+[error]

Reply via email to