From 3f26d37d17907b471b8f4b962ad399bbbf6d94af Mon Sep 17 00:00:00 2001 From: Sergio Date: Fri, 4 Sep 2026 13:09:17 +0000 Subject: [PATCH] =?UTF-8?q?granja:=20guardi=C3=A1n=20de=20disco=20en=20cam?= =?UTF-8?q?pana-deuda=20=E2=80=94=20corta=20en=20vez=20de=20anotar=20?= =?UTF-8?q?=E2=9C=97=20falsos?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit La cascada KDE del 2026-09-03 llenó el disco en la receta 52 de 71 y siguió moliendo: las 8 últimas murieron en 1-20 s con «No space left on device» y el bucle las anotó ✗, el mismo símbolo que una receta que no compila. El grafo del día siguiente decía «8 en deuda, clase c» y mandó a buscar un bug inexistente. Mide el MÍNIMO de dos filesystems, no uno: en gioser store/ (/dev/sdb, 109 G) y work/ (/dev/sdc, 53 G) son discos distintos y el que se llenó fue el de work/, donde vive el árbol de fuentes. Mirando sólo el store el vigía habría leído 109 G y dejado moler igual. Al cruzar el suelo corta con exit 3 y lista las recetas SIN INTENTAR, que no es lo mismo que fallidas. --- scripts/farm/campana-deuda.sh | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) diff --git a/scripts/farm/campana-deuda.sh b/scripts/farm/campana-deuda.sh index 0e43c2d1..6a82c712 100755 --- a/scripts/farm/campana-deuda.sh +++ b/scripts/farm/campana-deuda.sh @@ -72,12 +72,47 @@ echo "$(ts) campaña-deuda: lock de build tomado (el worker-loop espera su turno total=$(echo "$DEUDA" | wc -w); i=0; ok=0; fail=0; falladas="" echo "$(ts) campaña-deuda arranca: $total recetas | go=$(command -v go || echo AUSENTE)" | tee -a "$LOG" +# ── GUARDIÁN DE DISCO (2026-09-04) ────────────────────────────────────────────────────────────── +# POR QUÉ. La cascada KDE del 2026-09-03 llenó el disco en la receta 52 de 71 y siguió moliendo: las +# 8 restantes murieron cada una en 1-20 s con `error in backend: IO failure on output stream: No +# space left on device`, y el bucle las anotó `✗` — el MISMO símbolo que una receta que de verdad no +# compila. Al día siguiente el grafo decía «8 en deuda, clase c» y eso mandó a leer recetas durante +# un rato buscando un bug que no existía. Un disco lleno no es un fallo de receta: es un fallo de la +# MÁQUINA, y confundirlos cuesta el triaje entero. +# +# Además el trabajo posterior al corte es puro desperdicio: 4 minutos quemados produciendo ✗. +# +# DÓNDE SE MIDE: en DOS filesystems, no en uno. En gioser `store/` y `work/` son discos DISTINTOS +# (`/dev/sdb` el store, `/dev/sdc` el resto del repo) y el que se llenó esa noche fue el de `work/`: +# ahí viven `work/sources/-` y el árbol de build, que es lo que crece ~300 MB por +# receta. Un guardián que mirara sólo el store habría leído 109 G libres y dejado moler igual — +# el fallo clásico de calibrar el vigía en una escala que el dato nunca alcanza. Se toma el MÍNIMO +# de los dos: cualquiera de los dos lleno mata el build igual. +# +# Suelo en GiB. 8 G es ~1,5 veces el árbol+build de la receta más gorda que vimos (kwin, qt6); por +# debajo de eso el siguiente build es una apuesta, no una tarea. +SUELO_GIB="${SUELO_GIB:-8}" +avail_gib() { df -B1G --output=avail "$1" 2>/dev/null | tail -1 | tr -d ' '; } +libres_gib() { + a="$(avail_gib "$STORE")"; b="$(avail_gib "$HAMMER_DIR/work")" + [ -n "$a" ] || { echo "$b"; return; } + [ -n "$b" ] || { echo "$a"; return; } + [ "$a" -le "$b" ] && echo "$a" || echo "$b" +} + for r in $DEUDA; do i=$((i + 1)) f="recipes/$r.toml" [ -r "$f" ] || f="$(ls recipes/incoming*/"$r".toml 2>/dev/null | head -1)" [ -n "$f" ] && [ -r "$f" ] || { echo "$(ts) [$i/$total] $r ⚠ sin receta" | tee -a "$LOG"; continue; } echo "$(ts) [$i/$total] → $r" | tee -a "$LOG" + libres="$(libres_gib)" + if [ -n "$libres" ] && [ "$libres" -lt "$SUELO_GIB" ]; then + echo "$(ts) [$i/$total] ⛔ DISCO: ${libres} G libres (min de store=$(avail_gib "$STORE") y work=$(avail_gib "$HAMMER_DIR/work")) < suelo ${SUELO_GIB} G ⇒ corto la campaña" | tee -a "$LOG" + echo "$(ts) campaña-deuda ABORTADA por disco: $ok selladas, $fail fallidas, $((total - i + 1)) SIN INTENTAR" | tee -a "$LOG" + echo "$(ts) sin intentar: $(echo "$DEUDA" | tr ' ' '\n' | tail -n +"$i" | tr '\n' ' ')" | tee -a "$LOG" + exit 3 + fi # heartbeat: el dead-man ya cuenta `hammer build` como trabajo, pero entre receta y receta hay # huecos (fetch/vendor) donde no hay proceso con ese nombre. Tocarlo evita una muerte espuria. touch /run/hammer-heartbeat 2>/dev/null || true