Copilot commented on code in PR #13681:
URL: https://github.com/apache/trafficserver/pull/13681#discussion_r4011438468


##########
tests/gold_tests/pluginTest/rate_limit/rate_limit_sni_queue_client.sh:
##########
@@ -20,59 +20,158 @@
 # and check that the active-slot counter stays balanced and the server 
survives.
 #
 #   1. holder completes its handshake and holds the single slot (counter = 1);
-#   2. one connection enqueues because the slot is full, then closes while 
parked;
-#   3. the sweep reserves a slot and resumes a queued connection;
-#   4. the holder is closed and releases its slot;
+#   2. one connection enqueues because the slot is full and stays parked at 
the ClientHello hook;
+#   3. the sweep runs while the slot is still held and must leave the queued 
connection parked;
+#   4. the holder is closed and releases its slot; the next sweep reserves 
that slot and resumes
+#      the queued connection, which is then closed and releases the slot it 
now owns;
 #   5. a probe connection reserves the freed slot.
 #
-# Against the plugin before 508c1bea26 this aborts the server: the sweep 
resumed a queued
-# connection without a reservation, whose close then decremented the counter 
unmatched until it
-# wrapped and reserve() tripped TSReleaseAssert(_active <= _limit). The test 
asserts the counter
-# never wraps and no signal is logged, so it pins that fix as well as this 
change.
+# Against the plugin before 508c1bea26 this aborts the server: the sweep 
resumed the queued
+# connection in step 3 without a reservation, so the holder's close in step 4 
landed the counter
+# on zero and the resumed connection's close, releasing a slot it never held, 
wrapped the counter
+# below zero; the probe's reserve() then tripped TSReleaseAssert(_active <= 
_limit). The test
+# asserts no release wraps and no signal is logged, so it pins that fix.
 #
-# args: host port sni
+# The connection is resumed and then closed, never closed while parked, 
because a client cannot
+# close a parked connection as far as ATS is concerned: while the ClientHello 
hook is invoked ATS
+# does not read the socket, so a FIN sits in the kernel until the sweep 
reenables the VC. The
+# close-while-queued branch of sni_limiter.cc is therefore out of scope here 
and cannot be driven
+# from a client; do not reintroduce a "closes while parked" step, it only ever 
resolved to the
+# resume-then-close path this script now drives deterministically.
+#
+# Every step waits for the plugin's own debug line in traffic.out rather than 
sleeping for a
+# guessed interval: on a loaded runner a fixed sleep let the second connection 
miss the holder
+# entirely, so the queue path was silently never exercised (#13679). Every 
connection reads
+# stdin from a FIFO that never delivers data, and is ended with kill -TERM on 
openssl's own PID.
+# Neither the moment a connection opens nor the moment it closes is left to 
s_client: how it
+# reacts to EOF on stdin differs between OpenSSL and LibreSSL, so a connection 
that must stay up
+# is given stdin that never becomes readable, and one that must go is killed 
outright. The plugin
+# only needs the VCONN_CLOSE that the resulting FIN produces.
+#
+# args: host port sni traffic_out
 set -u
 host="$1"
 port="$2"
 sni="$3"
+traffic_out="$4"
+
+OSSL="openssl s_client -connect ${host}:${port} -servername ${sni} -quiet"
 
-OSSL="openssl s_client -connect ${host}:${port} -servername ${sni} -quiet 
-no_ign_eof"
+# Ceiling per step. It only bounds a broken run; a healthy one moves on as 
soon as the line
+# appears.
+WAIT_LIMIT=30
 
-# Run a command in the background and terminate it after a deadline. coreutils 
"timeout" is not
-# available everywhere (notably macOS), so do it with sleep and kill.
-run_for() {
-  deadline="$1"
-  shift
-  "$@" &
-  target=$!
-  (
-    sleep "${deadline}"
-    kill -TERM "${target}" 2>/dev/null
-  ) &
+holder=""
+queued=""
+probe=""
+
+cleanup() {
+  kill -TERM ${holder:-} ${queued:-} ${probe:-} 2>/dev/null || true
 }
+trap cleanup EXIT
 
-# 1. Holder: hold the single slot. Its stdin is a FIFO kept open on fd 3, so 
we end the
-#    holder deterministically in step 4 (closing fd 3 -> EOF -> clean TLS 
close -> FIN).
+# Shared stdin for every connection: a FIFO held open read-write on fd 3 that 
nothing ever
+# writes to, so reads block and polls stay idle until the process is killed. 
Opening it
+# read-write does not block, and the connections inherit fd 3 rather than 
opening the path,
+# so the directory can go straight away.
 fifo_dir="$(mktemp -d "${TMPDIR:-/tmp}/rl_holder.XXXXXX")"
-fifo="${fifo_dir}/fifo"
-mkfifo "$fifo"
-${OSSL} <"$fifo" >/dev/null 2>&1 &
-exec 3<>"$fifo"
+mkfifo "${fifo_dir}/fifo"
+exec 3<>"${fifo_dir}/fifo"
 rm -rf "$fifo_dir"
-sleep 3 # let the holder reserve the one slot
 
-# 2. One queued connection: enqueues because the slot is full, then closes 
while still parked
-#    at the ClientHello hook.
-run_for 0.3 sh -c "${OSSL} </dev/null >/dev/null 2>&1"
-sleep 2 # >= 2 sweep periods (300ms each), so the sweep runs while the 
connection is queued
+# Poll traffic.out until the fixed string has appeared at least count times. 
Diags serialises
+# each debug line and flushes it whole, so polling can never see a 
half-written line and a line
+# is visible as soon as the plugin emits it. On timeout, fail loudly with what 
was expected and
+# what the log holds so the sandbox is diagnosable.
+wait_for() {
+  needle="$1"
+  count="$2"
+  end=$((SECONDS + WAIT_LIMIT))
+  while :; do
+    seen=$(grep -c -F -- "$needle" "$traffic_out" 2>/dev/null || true)
+    if [ "${seen:-0}" -ge "$count" ]; then
+      return 0
+    fi
+    if [ "$SECONDS" -ge "$end" ]; then
+      echo "timed out after ${WAIT_LIMIT}s waiting for occurrence ${count} of 
'${needle}' (saw ${seen:-0})" >&2
+      echo "--- tail of ${traffic_out}:" >&2
+      tail -n 20 "$traffic_out" >&2
+      exit 1
+    fi
+    sleep 0.1
+  done
+}
+
+# Close a connection by killing openssl itself. Escalate to KILL rather than 
waiting forever:
+# a TERM-immune s_client would otherwise stall the run until autest's process 
timeout, turning a
+# seconds-long failure into a ten-minute one.
+#
+# The whole body is redirected because the shell announces a signal-killed 
background job on the
+# script's stderr as it reaps it ("Terminated: 15"), at whatever command 
happens to be running --
+# redirecting the wait alone does not catch it. That noise would sit in 
stream.stderr.txt next to
+# this script's real diagnostics, so keep it out and report an escalation on 
the saved fd instead.
+end_connection() {
+  exec 4>&2
+  {
+    kill -TERM "$1" 2>/dev/null || true
+    waited=0
+    while kill -0 "$1" 2>/dev/null; do
+      if [ "$waited" -ge 30 ]; then
+        echo "pid $1 ignored TERM after 3s; escalating to KILL" >&4
+        kill -KILL "$1" 2>/dev/null || true
+        break
+      fi
+      waited=$((waited + 1))
+      sleep 0.1
+    done
+    wait "$1" || true
+  } 2>/dev/null
+  exec 4>&-
+}
+
+# 1. Holder: take the single slot.
+${OSSL} <&3 >/dev/null 2>&1 &
+holder=$!
+wait_for 'Reserving a slot, active entities == 1' 1
+
+# 2. Queued connection: the slot is full, so it is parked at the ClientHello 
hook.
+${OSSL} <&3 >/dev/null 2>&1 &
+queued=$!
+wait_for 'Queueing the VC, we are at capacity' 1
+
+# 3. Let the sweep (every 300ms) run while the slot is still held. This is a 
lower bound, not a
+#    race: a correct sweep leaves the connection parked, which produces no 
line to wait for,
+#    and a slower runner only gives it more sweeps.
+sleep 1
+
+# A resume here means the sweep dequeued while the limiter was still full, 
which is the bug this
+# test pins. Only report it: the run must continue so the wrapped counter and 
the abort still
+# reach the test's traffic.out assertions, but naming the first wrong event 
keeps it from being
+# buried behind the probe's timeout 30s later.
+resumed=$(grep -c -F -- 'Enabling queued VC' "$traffic_out" 2>/dev/null || 
true)
+if [ "${resumed:-0}" -ne 0 ]; then
+  echo "the sweep resumed the queued connection while the limiter was still 
full" >&2
+fi

Review Comment:
   This branch only prints a diagnostic and continues, so an `Enabling queued 
VC` emitted during the preceding full-slot interval is still counted by the 
later `wait_for` at line 162. The test therefore does not enforce the ordering 
it claims to verify: a regression that resumes while the limiter is full can 
pass this precondition as long as it avoids the specific counter wrap. Make an 
early resume fail the client (or otherwise compare the event count before and 
after the holder release) instead of merely reporting it.



-- 
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]

Reply via email to