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 a64f2baa0 fix(standalone): stop aborting stream connections before the 
first config arrives (#13855)
a64f2baa0 is described below

commit a64f2baa06b1c7d55e53051f3430f39bf9f4024c
Author: AlinsRan <[email protected]>
AuthorDate: Fri Aug 28 08:50:46 2026 +0800

    fix(standalone): stop aborting stream connections before the first config 
arrives (#13855)
---
 apisix/admin/standalone.lua      | 10 +++++--
 apisix/stream/router/ip_port.lua |  8 +++++-
 t/admin/standalone-stream.t      | 57 ++++++++++++++++++++++++++++++++++++++++
 3 files changed, 72 insertions(+), 3 deletions(-)

diff --git a/apisix/admin/standalone.lua b/apisix/admin/standalone.lua
index 46f9a8001..7e69b5902 100644
--- a/apisix/admin/standalone.lua
+++ b/apisix/admin/standalone.lua
@@ -333,7 +333,10 @@ function _M.init_worker()
 
     -- due to the event module can not broadcast events between http and 
stream subsystems,
     -- we need to poll the shared dict to keep the config in sync
-    local last_modified_per_worker
+    -- The timestamp only has second resolution, so two updates landing in the
+    -- same second are indistinguishable by it and the later one would never
+    -- reach this worker. The digest changes with the content, so compare both.
+    local last_modified_per_worker, digest_per_worker
     timer_every(1, function ()
         if not exiting() then
             local config, err = get_config()
@@ -343,9 +346,12 @@ function _M.init_worker()
                 end
             else
                 local last_modified = config[METADATA_LAST_MODIFIED]
-                if last_modified_per_worker ~= last_modified then
+                local digest = config[METADATA_DIGEST]
+                if last_modified_per_worker ~= last_modified
+                   or digest_per_worker ~= digest then
                     update_config(config)
                     last_modified_per_worker = last_modified
+                    digest_per_worker = digest
                 end
             end
         end
diff --git a/apisix/stream/router/ip_port.lua b/apisix/stream/router/ip_port.lua
index 45e8e5531..1757824f9 100644
--- a/apisix/stream/router/ip_port.lua
+++ b/apisix/stream/router/ip_port.lua
@@ -151,7 +151,13 @@ do
         local _, cur_svc_ver = service_mod.services()
         if router_ver ~= user_routes.conf_version
            or service_ver ~= cur_svc_ver then
-            local err = create_router(user_routes.values)
+            -- `values` is nil until the config source has delivered
+            -- /stream_routes for the first time, which in standalone mode can
+            -- happen after this subsystem is already accepting connections.
+            -- Treat that as "no stream route" instead of indexing a nil table:
+            -- the error would be thrown before router_ver is assigned, so 
every
+            -- later connection would take this branch and abort again.
+            local err = create_router(user_routes.values or {})
             if err then
                 return false, "failed to create router: " .. err
             end
diff --git a/t/admin/standalone-stream.t b/t/admin/standalone-stream.t
new file mode 100644
index 000000000..27c90814e
--- /dev/null
+++ b/t/admin/standalone-stream.t
@@ -0,0 +1,57 @@
+#
+# Licensed to the Apache Software Foundation (ASF) under one or more
+# contributor license agreements.  See the NOTICE file distributed with
+# this work for additional information regarding copyright ownership.
+# The ASF licenses this file to You under the Apache License, Version 2.0
+# (the "License"); you may not use this file except in compliance with
+# the License.  You may obtain a copy of the License at
+#
+#     http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
+use t::APISIX 'no_plan';
+
+repeat_each(1);
+log_level('info');
+no_long_string();
+no_root_location();
+no_shuffle();
+
+add_block_preprocessor(sub {
+    my ($block) = @_;
+
+    if (!defined $block->yaml_config) {
+        $block->set_value("yaml_config", <<'_EOC_');
+deployment:
+    role: traditional
+    role_traditional:
+        config_provider: yaml
+    admin:
+        admin_key:
+            - name: admin
+              key: edd1c9f034335f136f87ad84b625c8f1
+              role: admin
+_EOC_
+    }
+
+    $block->set_value("stream_enable", 1);
+
+    if (!$block->stream_request) {
+        $block->set_value("stream_request", "mmm");
+    }
+});
+
+run_tests();
+
+__DATA__
+
+=== TEST 1: stream connection before the first config is pushed
+--- error_log
+not hit any route
+--- no_error_log
+attempt to index local 'tab'

Reply via email to