Copilot commented on code in PR #13904:
URL: https://github.com/apache/apisix/pull/13904#discussion_r3911244003


##########
apisix/admin/standalone.lua:
##########
@@ -64,19 +120,105 @@ local function update_and_broadcast_config(apisix_yaml)
 
     if shared_dict then
         -- the worker that handles Admin API calls is responsible for writing 
the shared dict
-        local ok, err = shared_dict:set("config", raw)
+        local stored = encode_config(apisix_yaml[METADATA_DIGEST], raw)
+        local ok, err = shared_dict:set("config", stored)
         if not ok then
             return nil, "failed to save config to shared dict: " .. err
         end
         core.log.info("standalone config updated: ", raw)
     else
         core.log.crit(config_yaml.ERR_NO_SHARED_DICT)
     end
-    return events:post(EVENT_UPDATE, EVENT_UPDATE)
+    return true
 end
 
 local validate_configuration = config_validate.validate_configuration
 
+
+local MAX_WAIT_MS = 60000
+local POLL_INTERVAL = 0.05
+
+
+local function parse_wait_ms(ctx)
+    local args = core.request.get_uri_args(ctx)
+    local wait = args and tonumber(args.wait)

Review Comment:
   `get_uri_args` can return `true` for `?wait` and a table for repeated 
parameters such as `?wait=1&wait=2`; passing either to `tonumber` raises a Lua 
argument error and turns an otherwise accepted update into a 500 response. 
Type-check the scalar before conversion, preserving the current zero-wait 
behavior for invalid values.



##########
apisix/admin/standalone.lua:
##########
@@ -40,22 +58,60 @@ local METADATA_DIGEST = "X-Digest"
 
 local _M = {}
 
+
+-- the "config" key in the standalone-config shared dict stores
+-- "<digest length>\n<digest><json body>" instead of plain JSON, so a worker
+-- can compare digests without decoding the (potentially large) JSON on
+-- every poll
+-- the reason for combining them into a single string and writing them to
+-- only one key is to ensure concurrent safety for updates by multiple
+-- workers
+local CONFIG_DIGEST_LENGTH_SEPARATOR = "\n"
+
+local function encode_config(digest, raw)
+    digest = digest or ""
+    return #digest .. CONFIG_DIGEST_LENGTH_SEPARATOR .. digest .. raw
+end
+
+
+local function decode_config(stored)
+    local idx = str_find(stored, CONFIG_DIGEST_LENGTH_SEPARATOR, 1, true)
+    if not idx then
+        return nil, nil, "missing digest length prefix"

Review Comment:
   The shared dictionary survives reloads, but released versions store `config` 
as plain JSON. Rejecting any value without this prefix means new workers cannot 
restore the active configuration on upgrade; additionally, old graceful workers 
cannot decode a prefixed value written during a mixed-version reload. Introduce 
a migration/versioning strategy that remains readable in both directions rather 
than replacing the format in place.



##########
apisix/admin/standalone.lua:
##########
@@ -313,6 +476,14 @@ end
 
 
 function _M.init_worker()
+    local ok, err = try_restore_from_shared_dict()

Review Comment:
   This restore is immediately discarded during startup. Both HTTP and stream 
call `admin.init_worker()` before `core.config.init_worker()` 
(`apisix/init.lua:140-147` and `1361-1364`), while YAML API mode resets 
`apisix_yaml` to `{}` in `config_yaml.lua:555-557`. Consequently, the later 
`core.config.new` calls still synchronously see an empty configuration, so the 
empty-config reload window remains. Please preserve the restored table or 
reorder initialization so restoration occurs after that reset and before 
resource initialization.



##########
apisix/core/config_local.lua:
##########
@@ -68,4 +69,29 @@ function _M.local_conf(force)
 end
 
 
+
+---
+-- Check whether the stream subsystem is enabled.
+-- Reads `apisix.proxy_mode` from the local config and looks for the
+-- substring "stream" in it, so it covers both `proxy_mode: stream` and
+-- `proxy_mode: http&stream`. `proxy_mode` doesn't change once a worker is
+-- running, so the result is computed once and cached.
+--
+-- @function require("core.config_local").is_stream_enabled
+-- @treturn boolean Whether the stream subsystem is enabled.
+-- @usage
+-- local config_local = require("core.config_local")

Review Comment:
   The added usage example imports a module path that does not exist, so 
copying it raises a module-not-found error; production call sites import 
`apisix.core.config_local`. The LDoc function annotation should also follow the 
existing `core.config_local.local_conf` form above rather than embedding 
`require(...)`.



##########
apisix/admin/standalone.lua:
##########
@@ -64,19 +120,105 @@ local function update_and_broadcast_config(apisix_yaml)
 
     if shared_dict then
         -- the worker that handles Admin API calls is responsible for writing 
the shared dict
-        local ok, err = shared_dict:set("config", raw)
+        local stored = encode_config(apisix_yaml[METADATA_DIGEST], raw)
+        local ok, err = shared_dict:set("config", stored)
         if not ok then
             return nil, "failed to save config to shared dict: " .. err
         end
         core.log.info("standalone config updated: ", raw)
     else
         core.log.crit(config_yaml.ERR_NO_SHARED_DICT)
     end
-    return events:post(EVENT_UPDATE, EVENT_UPDATE)
+    return true
 end
 
 local validate_configuration = config_validate.validate_configuration
 
+
+local MAX_WAIT_MS = 60000
+local POLL_INTERVAL = 0.05
+
+
+local function parse_wait_ms(ctx)
+    local args = core.request.get_uri_args(ctx)
+    local wait = args and tonumber(args.wait)
+    if not wait or wait <= 0 then
+        return 0
+    end
+    if wait > MAX_WAIT_MS then
+        return MAX_WAIT_MS
+    end
+    return wait

Review Comment:
   Please document the new `wait` parameter and its 200/202 response semantics 
in `docs/en/latest/deployment-modes.md`. That public guide currently only shows 
an unqualified PUT, and line 176 still states that workers start with an empty 
configuration, which this PR intentionally changes by restoring from shared 
memory.



##########
apisix/admin/standalone.lua:
##########
@@ -64,19 +120,105 @@ local function update_and_broadcast_config(apisix_yaml)
 
     if shared_dict then
         -- the worker that handles Admin API calls is responsible for writing 
the shared dict
-        local ok, err = shared_dict:set("config", raw)
+        local stored = encode_config(apisix_yaml[METADATA_DIGEST], raw)
+        local ok, err = shared_dict:set("config", stored)
         if not ok then
             return nil, "failed to save config to shared dict: " .. err
         end
         core.log.info("standalone config updated: ", raw)
     else
         core.log.crit(config_yaml.ERR_NO_SHARED_DICT)
     end
-    return events:post(EVENT_UPDATE, EVENT_UPDATE)
+    return true
 end
 
 local validate_configuration = config_validate.validate_configuration
 
+
+local MAX_WAIT_MS = 60000
+local POLL_INTERVAL = 0.05
+
+
+local function parse_wait_ms(ctx)
+    local args = core.request.get_uri_args(ctx)
+    local wait = args and tonumber(args.wait)
+    if not wait or wait <= 0 then
+        return 0
+    end
+    if wait > MAX_WAIT_MS then
+        return MAX_WAIT_MS
+    end
+    return wait
+end
+
+
+local function all_workers_applied(target_digest)
+    if not status_shared_dict then
+        return false
+    end
+
+    local n = worker_count()
+    local check_stream = config_local.is_stream_enabled()
+    for key in pairs(APPLIED_CHECK_KEYS) do
+        for id = 0, n - 1 do
+            if HTTP_RESOURCE_KEYS[key] then

Review Comment:
   Only active configuration objects can publish these status keys. For 
example, `/protos` is created solely by the `grpc-transcode` plugin 
(`plugins/grpc-transcode/proto.lua:258-263`), so a valid deployment that 
disables that plugin never publishes `worker:*:http:protos`; every request with 
`wait>0` therefore times out with 202 even after all applicable configuration 
is loaded. Restrict checks to resource objects instantiated in this subsystem.



-- 
This is an automated message from the Apache Git Service.
To respond to the message, please log on to GitHub and use the
URL above to go to the specific comment.

To unsubscribe, e-mail: [email protected]

For queries about this service, please contact Infrastructure at:
[email protected]

Reply via email to