fix: test: Retry pipequeries on a transient EADDRINUSE in the pipelined test

On FreeBSD, the TCP connect() call can transiently fail with
EADDRINUSE under parallel CI load.  The netmgr already retries such
connects (see #3451), but it retries on the same socket, which is
already bound to the same ephemeral source port, so when the
four-tuple is genuinely busy (e.g. in TIME_WAIT) every retry fails
the same way.  pipequeries then exits with "request event result:
address in use", leaving raw.1 empty and failing the first check.

All eight requests share a single TCP dispatch, so the failed connect
means no query ever reached ns4 and its cache is still cold.  It is
therefore safe to run pipequeries again: a fresh process binds a new
ephemeral port, and the out-of-order check keeps its meaning.  Retry
for up to ten attempts, but only on this specific transient error.

Assisted-by: Claude Code:claude-fable-5

Merge branch 'mnowak/pipelined-retry-transient-eaddrinuse' into 'main'

See merge request isc-projects/bind9!12228
This commit is contained in:
Michal Nowak 2026-06-24 17:16:14 +02:00
commit 28cddaaba6
2 changed files with 27 additions and 1 deletions

View file

@ -32,7 +32,32 @@ n=1
ret=0
echo_i "check pipelined TCP queries ($n)"
pipequeries <input >raw.$n || ret=1
# On FreeBSD, the TCP connect() call can transiently fail with
# EADDRINUSE even after the netmgr retried it in place: the socket is
# already bound, so retrying on the same source port cannot help.
# pipequeries then bails out before any query is sent, which leaves
# the ns4 cache cold, so it is safe to simply run it again (and the
# out-of-order check below remains meaningful on a repeated run).
#
# This loop is a workaround for the pipequeries.c implementation. If
# pipequeries is ever rewritten in pure Python (using the test suite's
# own DNS machinery, which can pick a fresh source port per attempt),
# this retry should no longer be necessary and can be dropped.
pq_left=10
while :; do
ret=0
pipequeries <input >raw.$n 2>pipequeries.err.$n || ret=1
cat pipequeries.err.$n >&2
pq_left=$((pq_left - 1))
if [ $ret -eq 0 ] || [ $pq_left -le 0 ]; then
break
fi
if ! grep "address in use" pipequeries.err.$n >/dev/null; then
break
fi
echo_i "retrying pipequeries after a transient connect failure"
sleep 1
done
awk '{ print $1 " " $5 }' <raw.$n >output.$n
sort <output.$n >output-sorted.$n
diff ref output-sorted.$n || {

View file

@ -14,6 +14,7 @@ import pytest
pytestmark = pytest.mark.extra_artifacts(
[
"output*",
"pipequeries.err*",
"raw*",
"ans*/ans.run",
]