cazador del cuelgue en headless: NO reproducido en 202 corridas, y qué descarta

Dos corridas del banco de PGO se colgaron en 120.034 y 120.030 ms — exactamente
el timeout del arnés, o sea bloqueos y no lentitud. Dos de 56 (~3,5%).

NO SE REPRODUJO. 202 corridas en cuatro condiciones, cero cuelgues:

  un binario, un sandbox compartido ......... 30 · 0
  4 binarios alternando, un sandbox ......... 60 · 0  (descarta churn de caché)
  4 binarios, UN SANDBOX NUEVO POR CORRIDA .. 56 · 0  (descarta los namespaces)
  ídem con LAS PÁGINAS EXACTAS del banco .... 56 · 0  (descarta la página)

Si la tasa fuera 3,5%, ver cero en 202 tendría probabilidad ~0,06%. La tasa real
bajo estas condiciones no es ésa, y lo que falta está fuera de ellas.

UN ERROR DE MÉTODO QUE COSTÓ 146 CORRIDAS, anotado en el script para no
repetirlo: las tres primeras cazas usaron flex.html y tablas.html porque las
tenía a mano, cuando las colgadas habían sido en fuera-del-corpus.html y
del-corpus.html. Estuve probando una condición que NO era la observada,
creyendo que sí. Antes de concluir «no se reproduce», comprobar que el
instrumento reproduce lo que dice reproducir.

Lo que sí se aprendió, y acota: es TODO O NADA. En 202 corridas ninguna pasó
siquiera de 45 s — o terminan en ~20 s o se bloquean hasta el timeout. No es
degradación, es bloqueo. Sobrevive la hipótesis que no se puede probar
retroactivamente: contención transitoria de otra cosa en la máquina —que es
compartida con otros agentes— durante esas dos ventanas.

El script queda para que la PRÓXIMA vez se capture en el acto (wchan, syscall,
state, memoria y carga del host, y el log del navegador de esa corrida) en vez
de empezar de cero.
This commit is contained in:
Sergio
2026-09-08 10:33:13 +00:00
parent 5d708f5225
commit 11e2c5f3a2
+77
View File
@@ -0,0 +1,77 @@
#!/bin/bash
# cazar-cuelgue-headless.sh — cazador de un cuelgue INTERMITENTE de firefox en headless.
#
# ══ EL CASO, Y POR QUÉ ESTE SCRIPT SOBREVIVE AUNQUE NO LO RESOLVIÓ ═════════════════════════════
# El 2026-09-08, dos corridas del banco de PGO se colgaron: 120.034 y 120.030 ms, o sea EXACTAMENTE
# el `timeout 120` del arnés. No eran ruido de carga —una corrida normal son ~20 s— sino bloqueos
# hasta que las maté. Dos de 56 (~3,5 %).
#
# NO SE REPRODUJO. 202 corridas en cuatro condiciones, CERO cuelgues:
#
# un binario, un sandbox compartido ............... 30 · 0
# 4 binarios alternando, un sandbox ............... 60 · 0 (descarta churn de caché de página)
# 4 binarios, UN SANDBOX NUEVO POR CORRIDA ........ 56 · 0 (descarta el montaje de namespaces)
# ídem, con LAS PÁGINAS EXACTAS del banco ......... 56 · 0 (descarta la página)
#
# Si la tasa fuera 3,5 %, ver cero en 202 tendría probabilidad ~0,06 %. O sea que la tasa real bajo
# estas condiciones NO es ésa, y lo que faltaba estaba fuera de ellas.
#
# ⚠ UN ERROR DE MÉTODO QUE COSTÓ 146 CORRIDAS, ANOTADO PARA NO REPETIRLO: las tres primeras cazas
# usaron `flex.html` y `tablas.html` porque las tenía a mano, cuando las colgadas habían sido en
# `fuera-del-corpus.html` y `del-corpus.html`. Estuve probando una condición que NO era la
# observada, creyendo que sí. Antes de concluir «no se reproduce», comprobar que el instrumento
# reproduce lo que dice reproducir.
#
# LO QUE SÍ SE APRENDIÓ, que acota al culpable:
# · Es TODO O NADA. En 202 corridas ninguna pasó siquiera de 45 s: o terminan en ~20 s o se
# bloquean hasta el timeout. No es lentitud ocasional, es un bloqueo.
# · Sobrevive la hipótesis que no se puede probar retroactivamente: contención transitoria de otra
# cosa en la máquina (es compartida con otros agentes) durante esas dos ventanas.
#
# ══ PARA QUÉ SIRVE ENTONCES ════════════════════════════════════════════════════════════════════
# Para que la PRÓXIMA vez que aparezca, se capture en el acto en vez de empezar de cero: `wchan`,
# `syscall` y `state` de cada proceso (distinguen espera de disco de espera de lock), memoria y
# carga del host en ese instante, y el log del navegador de esa corrida —que en las buenas se tira—.
#
# Uso: N=56 UMBRAL=45 scripts/diagnostico/cazar-cuelgue-headless.sh
# (necesita el rootfs hidratado en store/.rootfs/sway-v2 y las páginas en el scratchpad;
# ajustar las rutas de arriba si se mueven)
set -u
RAIZ=/mnt/vvv/hammer; R=$RAIZ/store/.rootfs/sway-v2
S=/tmp/claude-1001/-mnt-vvv-hammer/9d639cb7-92ff-4a26-bd7f-ec9988b972cf/scratchpad
N=${N:-56}; UMBRAL=${UMBRAL:-45}
FF=($(ls -d $RAIZ/store/8116bdec*-firefox $RAIZ/store/1d730334*-firefox \
$RAIZ/store/3d199174*-firefox $RAIZ/store/bb33fd56*-firefox))
PAGS=(fuera-del-corpus.html del-corpus.html) # LAS DEL BANCO: es donde ocurrieron las 2 colgadas
colgadas=0
for i in $(seq 1 $N); do
art=${FF[$(( i % 4 ))]}; pag=${PAGS[$(( i % 2 ))]}
bwrap --ro-bind "$R" / --ro-bind /usr/lib/musl/lib/libc.so /lib/ld-musl-x86_64.so.1 \
--ro-bind "$art/usr/lib/firefox" /usr/lib/firefox \
--dev /dev --proc /proc --tmpfs /run --tmpfs /tmp --unshare-pid \
--ro-bind "$S/bench" /bench --bind "$S/caza4" /salida --unshare-user --uid 0 --gid 0 \
--setenv PATH /usr/bin:/bin \
/bin/sh -c 'export HOME=/tmp/h MOZ_HEADLESS=1 MOZ_DISABLE_CONTENT_SANDBOX=1 LIBGL_ALWAYS_SOFTWARE=1 LANG=C.UTF-8; mkdir -p $HOME
LD_LIBRARY_PATH=/usr/lib/firefox /usr/lib/firefox/firefox --headless --screenshot /salida/x.png "file:///bench/'"$pag"'"' \
>/dev/null 2>&1 &
BW=$!
t=0
while kill -0 $BW 2>/dev/null && [ $t -lt $UMBRAL ]; do sleep 1; t=$((t+1)); done
if kill -0 $BW 2>/dev/null; then
colgadas=$((colgadas+1))
D=$S/caza4/colgada-$i; mkdir -p "$D"
{ echo "corrida $i · $(basename $art | cut -c1-12) · $pag · colgada a los ${t}s"
echo "--- memoria del host ---"; head -4 /proc/meminfo
echo "--- carga ---"; cat /proc/loadavg; } > "$D/resumen.txt"
ps -eo pid,ppid,stat,rss,wchan:24,comm 2>/dev/null | grep -iE 'bwrap|firefox|Web Con|Isolated|PID' > "$D/ps.txt"
for p in $(pgrep -P $BW 2>/dev/null; pgrep -f 'usr/lib/firefox/firefox' 2>/dev/null | head -8); do
{ echo "--- pid $p comm=$(cat /proc/$p/comm 2>/dev/null) state=$(awk '/^State:/{print $2,$3}' /proc/$p/status 2>/dev/null)"
echo " wchan=$(cat /proc/$p/wchan 2>/dev/null)"
echo " syscall=$(cut -c1-70 /proc/$p/syscall 2>/dev/null)"
echo " rss=$(awk '/^VmRSS:/{print $2,$3}' /proc/$p/status 2>/dev/null)"; } >> "$D/procesos.txt" 2>/dev/null
done
kill -TERM $BW 2>/dev/null; sleep 3; kill -KILL $BW 2>/dev/null
printf 'X'
else printf '.'; fi
done
echo ""; echo "== colgadas: $colgadas de $N"