AlinsRan opened a new pull request, #13855:
URL: https://github.com/apache/apisix/pull/13855
### Description
In standalone mode the stream subsystem starts accepting connections before
any configuration has reached it, and every connection that arrives in that
window is aborted:
```
2026/08/19 10:03:22 [error] 49#49: *4026 lua entry thread aborted: runtime
error:
/usr/local/apisix/apisix/core/config_util.lua:37: attempt to index local
'tab' (a nil value)
stack traceback:
/usr/local/apisix/apisix/core/config_util.lua: in function '(for
generator)'
/usr/local/apisix/apisix/stream/router/ip_port.lua:76: in function
'create_router'
/usr/local/apisix/apisix/stream/router/ip_port.lua:154: in function 'match'
/usr/local/apisix/apisix/init.lua:1328: in function 'stream_preread_phase'
```
The client sees an empty response, not an error, so this shows up as a
stream port that silently answers nothing.
**Why `values` is nil.** `config_yaml.sync_data` returns early while the
config source has not delivered anything yet:
```lua
conf_version = apisix_yaml[self.conf_version_key] or 0 -- 0, apisix_yaml
is empty
if not conf_version or conf_version == self.conf_version then
return true -- values is
left nil
end
```
`user_routes` starts as `{conf_version = 0, values = nil}`, so `values`
stays nil while `conf_version` is 0.
**Why the connection aborts.** In `ip_port.lua`, `router_ver` starts as nil,
so `router_ver ~= user_routes.conf_version` (`nil ~= 0`) holds and
`create_router(user_routes.values)` is called with nil. The error is thrown
before `router_ver` is assigned, so the next connection takes the same branch
and aborts again — the worker does not leave this state on its own.
**Why it does not recover.** Only the polling loop in `admin/standalone.lua`
carries configuration across the http/stream boundary (the events module cannot
broadcast between subsystems, as the comment there says). It skipped an update
whenever `X-Last-Modified` was unchanged, and that field is `ngx_time()` —
second resolution. Two updates inside one second are indistinguishable by it,
so if the poll ran between them the second update was dropped permanently:
nothing re-delivers it later, because a client re-sending the same content is
answered with `config not changed: same digest` and the stored metadata never
advances.
That combination is what I hit in the field: a control plane pushed several
configs inside one second, the first without `stream_routes` and a later one
with them, and the stream workers of that instance answered nothing for the
rest of the pod's life while the HTTP subsystem and the Admin API both showed
the stream route present.
### Fix
- `apisix/stream/router/ip_port.lua`: treat a missing route list as "no
stream route" instead of indexing nil, so the connection takes the normal
no-match path and the router is rebuilt once the routes arrive.
- `apisix/admin/standalone.lua`: compare `X-Digest` as well as
`X-Last-Modified` in the polling loop. The digest changes with the content, so
a second update inside the same second is no longer invisible to the other
subsystem.
### Reproduce
No control plane needed:
```yaml
# config.yaml
deployment:
role: traditional
role_traditional:
config_provider: yaml
admin:
admin_key:
- key: edd1c9f034335f136f87ad84b625c8f1
name: admin
role: admin
nginx_config:
worker_processes: 2
error_log_level: info
apisix:
proxy_mode: http&stream
stream_proxy:
tcp:
- 9100
```
```shell
docker run -d --name apisix-repro -p 19180:9180 -p 19100:9100 \
-v $PWD/config.yaml:/usr/local/apisix/conf/config.yaml:ro apache/apisix:dev
# connect to the stream port before pushing any configuration
printf 'GET / HTTP/1.1\r\nHost: localhost\r\nConnection: close\r\n\r\n' | nc
127.0.0.1 19100
# -> empty response, and the trace above in logs/error.log
```
With this patch the same connection logs `ip_port.lua: match(): not hit any
route` and no error, and once a config carrying `stream_routes` is pushed the
connection is routed normally.
### Checklist
- [x] I have explained the need for this PR and the problem it solves
- [x] I have explained the changes or the new features added to this PR
- [x] I have added tests corresponding to this change
- [x] I have updated the documentation to reflect this change — n/a, no
user-facing interface changed
- [x] I have verified that this change is backward compatible
Note on the test: `t/admin/standalone-stream.t` asserts that a stream
connection made before the first config push takes the no-match path and logs
no abort. I could not run test-nginx locally — the openresty on this machine
predates `apisix_stream_metrics_zone`, so pre-existing stream tests such as
`t/config-center-yaml/stream-route.t` fail to start nginx here as well. The
behaviour was verified against `apache/apisix:dev` with the two patched files
mounted in, as described above.
--
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]