]> git.ipfire.org Git - thirdparty/rspamd.git/commit
[Test] functional: wait for rspamd ports to free in teardown
authorVsevolod Stakhov <vsevolod@rspamd.com>
Mon, 1 Jun 2026 14:30:16 +0000 (15:30 +0100)
committerVsevolod Stakhov <vsevolod@rspamd.com>
Mon, 1 Jun 2026 14:30:16 +0000 (15:30 +0100)
commit65f4e09cea669fd7ef379e9bdf6f929b0c00bec9
treeceb31f25fe135af4f3780d0477846620e6459ac1
parentc3e7a93b22595b3aaaad9118df3e16b60072bdd5
[Test] functional: wait for rspamd ports to free in teardown

Under pabot each worker runs many suites sequentially on the SAME port
range (base + worker_index*100). Rspamd Teardown did Terminate Process +
Wait For Process, but that only reaps the MAIN rspamd; the listening
sockets are shared with forked workers and can linger a beat after main
exits. The next suite's rspamd on that worker then races them and dies:

  rspamd_inet_address_listen: bind 127.0.0.1:57090 failed: 98,
    'Address already in use'
  spawn_workers: cannot listen on normal socket 127.0.0.1:57090
  Process Is Gone (rc=1, port=57089)

which cascades the whole shared-rspamd suite (e.g. 001_merged -> 250+
failures) or single suites like 440_ssl_server. rspamd sets SO_REUSEADDR
before bind, so this is NOT TIME_WAIT -- it is a still-LISTENing socket
from a not-yet-fully-gone worker.

Add port_is_free() (rspamd.py) and a Wait For Rspamd Ports Released
keyword, called from Rspamd Teardown after Wait For Process: block (up to
~6s, warn-not-fail) until the normal + controller ports actually refuse
connections before releasing the suite. Closes the handoff race window.

This is a pre-existing flake (same bind-98 signature on master, e.g.
fedora job for #6067 with :56990), independent of the dummy-port
templating in this branch; both CI runs of this PR hit it in different
suites, the tell-tale of nondeterministic infra flake.

Verified: the keyword runs on every teardown (357 invocations / 714 port
checks in a 4-worker pabot run) and port_is_free correctly passes on a
free port and blocks on a live listener; no regression in serial or
parallel runs. The race itself is timing-dependent and reproduces under
CI container contention rather than locally, so CI is the real check.
test/functional/lib/rspamd.py
test/functional/lib/rspamd.robot