cazadores: setsid + matar el GRUPO — mataban el bwrap y el firefox sobrevivía

Segunda vez el mismo día: un firefox colgado de la verificación A/B se quedó con el lock de
build de la granja. La causa es del arnés, no de firefox: 'kill $BW' mata el bwrap EXTERIOR,
que no es el init del namespace de PID, así que el proceso de dentro sigue vivo — y si el
arnés corría bajo flock, hereda el fd del lock y deja a la granja muda hasta que alguien mire.

Medido con control negativo y positivo:
  matar sólo el bwrap  → queda 1 firefox vivo   (reproduce la fuga)
  setsid + matar grupo → quedan 0                (arreglado)

Se documenta además que el 'flock' del uso lleva '-o', que no es opcional.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QPJteswQvP1L7zSBrQQe2
This commit is contained in:
Sergio
2026-09-08 18:12:31 +00:00
co-authored by Claude Opus 5
parent 813a59db89
commit 0d7beefb60
3 changed files with 24 additions and 7 deletions
@@ -26,7 +26,9 @@
# matar a alguien es a él, nunca a un build de la granja. Y toda la caza corre con el flock de la
# granja tomado, para que no arranque una compilación de 4 h mientras se estrangula la RAM.
#
# Uso: N=20 scripts/diagnostico/cazar-cuelgue-bajo-presion.sh
# Uso: N=20 flock -o work/.farm-build.lock scripts/diagnostico/cazar-cuelgue-bajo-presion.sh
# El `-o` NO es opcional: sin él, un firefox que se cuelgue hereda el fd del lock y lo
# retiene DESPUÉS de que el arnés termine (regla 1 de CLAUDE.md). Pasó dos veces.
set -u
RAIZ=/mnt/vvv/hammer; R=$RAIZ/store/.rootfs/sway-v2
AQUI=$(cd "$(dirname "$0")" && pwd)
@@ -55,7 +57,12 @@ for NIVEL in 0 2000 1200 700; do
colg=0; muer=0; ok=0
for i in $(seq 1 $N); do
d0=$(disp); p0=$(psi); o0=$(ooms)
bwrap --ro-bind "$R" / --ro-bind /usr/lib/musl/lib/libc.so /lib/ld-musl-x86_64.so.1 \
# ⚠ `setsid` + matar el GRUPO, no el bwrap (medido 2026-09-08, DOS veces el mismo día).
# `kill $BW` mata el bwrap EXTERIOR, que no es el init del namespace de PID: el firefox de
# dentro sobrevive. Y si el arnés corría bajo `flock`, ese superviviente HEREDA el fd del lock
# y deja a la granja sin poder compilar hasta que alguien lo note — pasó hora y media, en
# silencio. Medido: matando sólo el bwrap queda 1 firefox vivo; matando el grupo, 0.
setsid bwrap --ro-bind "$R" / --ro-bind /usr/lib/musl/lib/libc.so /lib/ld-musl-x86_64.so.1 \
--ro-bind "${FF[$(( i % 2 ))]}/usr/lib/firefox" /usr/lib/firefox \
--dev /dev --proc /proc --tmpfs /run --tmpfs /tmp --unshare-pid \
--ro-bind "$AQUI" /bench --bind "$SAL" /salida --unshare-user --uid 0 --gid 0 \
@@ -80,7 +87,7 @@ for NIVEL in 0 2000 1200 700; do
echo " rss=$(awk '/^VmRSS:/{print $2,$3}' /proc/$p/status 2>/dev/null)"; } >>"$D/procesos.txt" 2>/dev/null
done
cp "$SAL/nav-$i.log" "$D/navegador.log" 2>/dev/null
kill -TERM $BW 2>/dev/null; sleep 3; kill -KILL $BW 2>/dev/null; printf 'X'
kill -TERM -$BW 2>/dev/null; sleep 3; kill -KILL -$BW 2>/dev/null # el GRUPO; printf 'X'
else
wait $BW; rc=$?
od=$(( $(ooms) - o0 ))
@@ -130,7 +130,12 @@ PAGS=(fuera-del-corpus.html del-corpus.html) # LAS DEL BANCO: es donde ocurrie
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 \
# ⚠ `setsid` + matar el GRUPO, no el bwrap: `kill $BW` mata el bwrap EXTERIOR, que no es el init
# del namespace de PID, y el firefox de dentro SOBREVIVE. Si el arnés corre bajo `flock`, ese
# superviviente hereda el fd del lock y deja a la granja sin compilar hasta que alguien lo note
# (pasó dos veces el 2026-09-08, hora y media la primera). Medido: matando sólo el bwrap queda
# 1 firefox vivo; matando el grupo, 0.
setsid 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 \
@@ -154,7 +159,7 @@ for i in $(seq 1 $N); do
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
kill -TERM -$BW 2>/dev/null; sleep 3; kill -KILL -$BW 2>/dev/null # el GRUPO
printf 'X'
else printf '.'; fi
done
@@ -26,7 +26,12 @@ for i in $(seq 1 $N); do
for k in viejo nuevo; do
[ $k = viejo ] && ENV="$VIEJO" || ENV="$NUEVO"
SAL=$(mktemp -d)
bwrap --ro-bind "$R" / --ro-bind /usr/lib/musl/lib/libc.so /lib/ld-musl-x86_64.so.1 \
# ⚠ `setsid` + matar el GRUPO, no el bwrap (medido 2026-09-08, DOS veces el mismo día).
# `kill $BW` mata el bwrap EXTERIOR, que no es el init del namespace de PID: el firefox de
# dentro sobrevive. Y si el arnés corría bajo `flock`, ese superviviente HEREDA el fd del lock
# y deja a la granja sin poder compilar hasta que alguien lo note — pasó hora y media, en
# silencio. Medido: matando sólo el bwrap queda 1 firefox vivo; matando el grupo, 0.
setsid bwrap --ro-bind "$R" / --ro-bind /usr/lib/musl/lib/libc.so /lib/ld-musl-x86_64.so.1 \
--ro-bind "$A/usr/lib/firefox" /usr/lib/firefox --dev /dev --proc /proc --tmpfs /run --tmpfs /tmp \
--unshare-pid --ro-bind "$AQUI" /bench --bind "$SAL" /salida --unshare-user --uid 0 --gid 0 \
--setenv PATH /usr/bin:/bin --setenv APAGA "$ENV" \
@@ -37,7 +42,7 @@ for i in $(seq 1 $N); do
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
COLG[$k]=$(( ${COLG[$k]} + 1 )); cp -r "$SAL" "$RAIZ/work/colgada-$k-$i" 2>/dev/null
kill -TERM $BW 2>/dev/null; sleep 3; kill -KILL $BW 2>/dev/null
kill -TERM -$BW 2>/dev/null; sleep 3; kill -KILL -$BW 2>/dev/null # el GRUPO
else
wait $BW 2>/dev/null
SEGV[$k]=$(( ${SEGV[$k]} + $(grep -c 'exited on signal 11' "$SAL/nav.log") ))