From 279d9e0036282bf5c092f9ae9b7d3ed6fb7cccf2 Mon Sep 17 00:00:00 2001 From: MDA2AV Date: Sun, 16 Aug 2026 14:02:23 +0100 Subject: [PATCH] validate: give the production stack its cache port, the way the benchmark driver does #1191 made the three production-stack compose files wait for a healthy cache, so a cache that cannot bind now fails the stack instead of being stepped over. It fixed the benchmark path at the same time, by having gateway_up stop the harness Redis sidecar before a stack that ships its own. It did not fix the validate path, because validate.sh does not source scripts/lib/gateway.sh - it carries its own compose handling. So validation started failing where it used to pass: dependency failed to start: container httparena-validate-gw-production-stack-fulmine-cache-1 exited (1) | Could not create server TCP listening socket 0.0.0.0:6379: bind: Address in use validate.sh starts its own Redis sidecar on 6379 whenever the entry subscribes to crud, and fulmine subscribes to both crud and production-stack. The cache has never been able to bind there either; the short depends_on form meant nobody found out, and validation was checking the stack against the sidecar's Redis rather than the one the profile configures. _prodstack_yield_redis stops the sidecar before the compose up and _prodstack_restore_redis brings it back, on the failure path as well as after teardown. The sidecar's docker run moves into redis_sidecar_start() so both callers use one definition rather than a copy. Entries that subscribe to production-stack without crud never set REDIS_CONTAINER, and the yield is a no-op for them. Verified by exercising the three functions against real containers: the sidecar is stopped and 6379 released, restarted afterwards, and left alone when REDIS_CONTAINER is unset. --- scripts/validate.sh | 62 +++++++++++++++++++++++++++++++++++---------- 1 file changed, 49 insertions(+), 13 deletions(-) diff --git a/scripts/validate.sh b/scripts/validate.sh index ecd0506a7..4e695a0ff 100755 --- a/scripts/validate.sh +++ b/scripts/validate.sh @@ -236,16 +236,9 @@ if has_test "async-db" || has_test "crud" || has_test "api-4" || has_test "api-1 docker_args+=(-e "DATABASE_MAX_CONN=256") fi -# Start Redis sidecar if needed -if has_test "crud"; then - - REDIS_CONTAINER="httparena-redis" - REDIS_URL="redis://localhost:6379" - # Validation is correctness-only, so the Redis sidecar is not pinned to specific - # cores by default (benchmarking pins it via scripts/lib/redis.sh). Set REDIS_CPUSET - # explicitly to restore pinning; left unset it runs unpinned and works on any host. - REDIS_CPUSET="${REDIS_CPUSET:-}" - +# A function rather than inline, because the production-stack check has to hand +# the port back and then restart it — see _prodstack_yield_redis below. +redis_sidecar_start() { echo "[redis] Starting Redis sidecar${REDIS_CPUSET:+ (cpuset=$REDIS_CPUSET)}" docker rm -f "$REDIS_CONTAINER" 2>/dev/null || true docker run -d --rm --name "$REDIS_CONTAINER" --network host \ @@ -265,14 +258,28 @@ if has_test "crud"; then >/dev/null # Wait for PING to succeed. + local i for i in $(seq 1 30); do if docker exec "$REDIS_CONTAINER" redis-cli ping 2>/dev/null | grep -q PONG; then echo "[redis] Ready" - break + return 0 fi - [ "$i" -eq 30 ] && { echo "FAIL: Redis sidecar not ready"; exit 1; } sleep 1 done + return 1 +} + +# Start Redis sidecar if needed +if has_test "crud"; then + + REDIS_CONTAINER="httparena-redis" + REDIS_URL="redis://localhost:6379" + # Validation is correctness-only, so the Redis sidecar is not pinned to specific + # cores by default (benchmarking pins it via scripts/lib/redis.sh). Set REDIS_CPUSET + # explicitly to restore pinning; left unset it runs unpinned and works on any host. + REDIS_CPUSET="${REDIS_CPUSET:-}" + + redis_sidecar_start || { echo "FAIL: Redis sidecar not ready"; exit 1; } docker_args+=(-e "REDIS_URL=$REDIS_URL") fi @@ -1948,6 +1955,31 @@ fi # api returns 401 without a cookie) and the authenticated path (api # returns 200 with a pre-seeded session cookie). +# The stack ships its own Redis on the host's 6379, and the validate sidecar +# above already holds it whenever the entry subscribes to crud — fulmine is +# subscribed to both. The benchmark driver handles this in gateway.sh +# (_gateway_yield_redis); validate.sh has its own compose handling and needs the +# same. It used to go unnoticed because the server depended on the cache with +# the short list form and started anyway; now that the compose files wait for a +# healthy cache, an unavailable port fails the stack instead of quietly +# validating against the wrong Redis. +PRODSTACK_STOPPED_REDIS=false + +_prodstack_yield_redis() { + PRODSTACK_STOPPED_REDIS=false + [ -n "${REDIS_CONTAINER:-}" ] || return 0 + docker ps --format '{{.Names}}' 2>/dev/null | grep -qx "$REDIS_CONTAINER" || return 0 + echo "[production-stack] stopping the validate redis sidecar: the stack ships its own cache on 6379" + docker rm -f "$REDIS_CONTAINER" >/dev/null 2>&1 || true + PRODSTACK_STOPPED_REDIS=true +} + +_prodstack_restore_redis() { + [ "$PRODSTACK_STOPPED_REDIS" = true ] || return 0 + PRODSTACK_STOPPED_REDIS=false + redis_sidecar_start || echo "[warn] redis sidecar did not come back up" +} + _validate_production_stack() { local compose_file="$1" local docs_url="$2" @@ -1957,9 +1989,10 @@ _validate_production_stack() { local gw_project="httparena-validate-gw-${profile}-${FRAMEWORK}" if [ -f "$compose_file" ]; then + _prodstack_yield_redis echo "[$profile] Building and starting compose stack..." CERTS_DIR="$CERTS_DIR" DATA_DIR="$DATA_DIR" DATABASE_URL="postgres://bench:bench@localhost:5432/benchmark" \ - docker compose -f "$compose_file" -p "$gw_project" up --build -d || { echo "FAIL: $profile compose up"; dump_stack_logs "$gw_project"; FAIL=$((FAIL + 1)); return; } + docker compose -f "$compose_file" -p "$gw_project" up --build -d || { echo "FAIL: $profile compose up"; dump_stack_logs "$gw_project"; _prodstack_restore_redis; FAIL=$((FAIL + 1)); return; } else echo " FAIL [$profile]: compose file not found at $compose_file" FAIL=$((FAIL + 1)) @@ -2155,6 +2188,9 @@ print(f'{count} {valid} {correct_totals}') CERTS_DIR="$CERTS_DIR" DATA_DIR="$DATA_DIR" DATABASE_URL="postgres://bench:bench@localhost:5432/benchmark" \ docker compose -f "$compose_file" -p "$gw_project" down --remove-orphans 2>/dev/null || true fi + # The stack has released 6379; give it back to the sidecar for whatever + # runs after this. + _prodstack_restore_redis } if has_test "production-stack"; then