From: Vsevolod Stakhov Date: Mon, 1 Jun 2026 16:32:43 +0000 (+0100) Subject: [Test] functional: also wait for SSL/proxy ports in teardown X-Git-Tag: 4.1.0~11^2~2 X-Git-Url: http://git.ipfire.org/gitweb/index.cgi?a=commitdiff_plain;h=a663e790df0559dfd731e16a1d1ff1e34a107b1e;p=thirdparty%2Frspamd.git [Test] functional: also wait for SSL/proxy ports in teardown The teardown port-release wait added in the previous commit only covered the normal + controller plain ports. The 440_ssl_server flake is the same race on a port it missed: a test rspamd binds up to five sockets (normal, controller, proxy, controller-SSL, normal-SSL), and a previous suite's controller-SSL listener could still hold its port when the next rspamd on that pabot worker started. The CI log shows it exactly: rspamd_fork_worker: prepare to fork process controller (0); listen on: 127.0.0.1:57190 rspamd_inet_address_listen: bind 127.0.0.1:57196 failed: 98, 'Address already in use' spawn_workers: cannot listen on normal socket 127.0.0.1:57196 57196 is PORT_CONTROLLER_SSL for that worker slot. main carried on and forked the controller with only its plain socket, so the SSL listener never came up and every HTTPS test hit connection-refused for the full retry budget -- the "slow SSL controller" the two prior band-aids tried to wait out. Extend Wait For Rspamd Ports Released to loop over all five ports. All RSPAMD_PORT_* vars are always defined in vars.py, and a port the current config never bound refuses connection immediately, so Port Is Free passes at once -- waiting on an unused port is a cheap no-op. Verified 001_merged (which owns 440_ssl_server) still passes serially with the SSL ports now checked in teardown; the SSL bind race is timing-dependent under CI contention, so the fedora/ubuntu runs are the real check. --- diff --git a/test/functional/lib/rspamd.robot b/test/functional/lib/rspamd.robot index bc3fd60276..f90bb85bff 100644 --- a/test/functional/lib/rspamd.robot +++ b/test/functional/lib/rspamd.robot @@ -334,18 +334,26 @@ Rspamd Redis Teardown Wait For Rspamd Ports Released [Documentation] Block until this suite's rspamd listening ports are ... free, so the next suite on the same pabot worker can rebind them. - ... Checks the always-present normal + controller ports; each is given - ... up to ~6s (matches a slow worker shutdown under CPU contention) and - ... failure to free is a warning, not a hard error -- we don't want a - ... stuck port to mask the real test result, just to close the common - ... handoff race. See port_is_free in rspamd.py for why SO_REUSEADDR - ... does not cover this. - Run Keyword And Warn On Failure - ... Wait Until Keyword Succeeds 30x 0.2s - ... Port Is Free ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_NORMAL} - Run Keyword And Warn On Failure - ... Wait Until Keyword Succeeds 30x 0.2s - ... Port Is Free ${RSPAMD_LOCAL_ADDR} ${RSPAMD_PORT_CONTROLLER} + ... Covers every port a test rspamd may bind: normal, controller, + ... proxy, and the two TLS listeners (controller + normal SSL). All + ... five RSPAMD_PORT_* vars are always defined in vars.py; a port the + ... current config never bound just refuses connection immediately, so + ... Port Is Free passes at once -- waiting on it is a cheap no-op. The + ... SSL listeners matter specifically: the 440_ssl_server flake was a + ... previous suite's controller-SSL socket lingering on its port, so + ... the next rspamd silently came up WITHOUT its SSL listener (it logs + ... bind 98 for the SSL port, then forks the controller with only the + ... plain socket) and every HTTPS test got connection-refused. + ... Each port gets up to ~6s; failure to free is a warning, not a hard + ... error, so a stuck port can't mask the real test result. See + ... port_is_free in rspamd.py for why SO_REUSEADDR does not cover this. + FOR ${port} IN + ... ${RSPAMD_PORT_NORMAL} ${RSPAMD_PORT_CONTROLLER} ${RSPAMD_PORT_PROXY} + ... ${RSPAMD_PORT_CONTROLLER_SSL} ${RSPAMD_PORT_NORMAL_SSL} + Run Keyword And Warn On Failure + ... Wait Until Keyword Succeeds 30x 0.2s + ... Port Is Free ${RSPAMD_LOCAL_ADDR} ${port} + END Run Redis ${RSPAMD_TMPDIR} = Make Temporary Directory