fix(granja): el cache de ZIG era el 91% de los inodos del worker, y nadie lo podaba
Cierra el agujero del commit anterior. Ahí se arregló que el watchdog mirara los inodos; faltaba decir QUIÉN se los comía, y la respuesta estaba en el sitio que ningún podador visitaba: `.dev-fs/cache/zig`, **~11,4 M de inodos de 12,5 M usados en el fs — el 91 %**. Ni `ls` terminaba ahí, y por eso el censo a mano no lo encontró: tres intentos de `du`/`find` se colgaron en ese subárbol y yo lo leí como «hay inodos fuera del alcance del contenedor, mirá el Proxmox». No había nada que mirar afuera; estaba acá. Es el punto ciego más caro posible: **zig es el compilador de TODO el corpus** (`compiler = "zig-cc"`), su cache global crece con cada build, y no lo tocaba nadie — ni este watchdog, que purgaba las caches Go y de zig no sabía nada, ni `poda-fuentes.sh`, ni la cosecha. Un montón que sólo crece y que ningún guardián nombra no se descubre: se choca. ⚠ `mv` y DESPUÉS `rm -rf` en segundo plano, no `rm -rf` directo: borrar millones de ficheros tarda minutos y un build que arranque en el medio escribiría dentro del árbol que se está borrando. El rename es atómico — el build siguiente encuentra el sitio vacío y crea su cache nuevo mientras el viejo se va en paralelo. Y va dentro de la misma guarda de «ningún build en vuelo» que protege a las caches Go, por el motivo que ya estaba escrito ahí. La ruta se resuelve como en `config.rs` (`cache_root = <proy>/.dev-fs/cache`) y respeta el mismo override `CACHE` que acepta el build: si alguien lo mueve, el watchdog lo sigue. Medido purgándolo a mano: 785 k → 989 k inodos libres y subiendo mientras borra, disco de 84% a 80%. Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Delta semántico
Ninguna definición cambió: el cambio es de texto, documentación o datos.
1 archivos · +27 −2
diff --git a/scripts/farm/farm-worker-loop.sh b/scripts/farm/farm-worker-loop.sh
index 4a963ada..273cacba 100755
--- a/scripts/farm/farm-worker-loop.sh
+++ b/scripts/farm/farm-worker-loop.sh
@@ -30,7 +30,9 @@ mkdir -p "$HAMMER_DIR/work"
# --- watchdog de disco: cada 3 min borra work/sources/* que ningún bwrap activo bind-monta
# (seguro: sellada=store CAS, en-cola=se re-extrae sola). Evita que el vendoring llene el disco.
-# Además, si el disco supera DISK_HIGH%, purga los CACHES Go (GOMODCACHE ~/go/pkg/mod + gocache):
+# Además, si la presión (bytes o INODOS) supera DISK_HIGH%, purga los CACHES Go (GOMODCACHE
+# ~/go/pkg/mod + gocache) **y el cache global de zig** (`.dev-fs/cache/zig`), que es el más gordo de
+# los tres y el que nadie podaba: 11,4 M de inodos de 12,5 M usados, medido el 2026-10-07.
# el `go mod vendor` de las recetas Go acumula ahí decenas de GB sin tope (el vendor/ local de cada
# receta ya tiene lo que el build necesita; el modcache sólo se usa DURANTE el vendor). Si la purga
# pisa un vendor en curso, esa receta reintenta el próximo ciclo (cache-hit el resto). Sin esto, una
@@ -120,8 +122,31 @@ SUELO_FRIO_MIN="${SUELO_FRIO_MIN:-120}"
if pgrep -f 'go mod vendor' >/dev/null 2>&1 || pgrep -f 'release/(takana|hammer).* build ' >/dev/null 2>&1; then
echo "$(date -u +%FT%TZ) watchdog: ${medidor} al ${use}% ≥ ${DISK_HIGH}% pero hay build en vuelo ⇒ difiero purga caches Go"
else
- echo "$(date -u +%FT%TZ) watchdog: ${medidor} al ${use}% ≥ ${DISK_HIGH}% ⇒ purgo caches Go"
+ echo "$(date -u +%FT%TZ) watchdog: ${medidor} al ${use}% ≥ ${DISK_HIGH}% ⇒ purgo caches Go y zig"
go clean -modcache 2>/dev/null; go clean -cache 2>/dev/null
+ # ── Y EL CACHE DE ZIG, que es el que de verdad tumbó esta caja (2026-10-07) ──────────────
+ # Este watchdog purgaba Go y de zig no sabía nada, y **zig es el compilador de TODO el
+ # corpus** (`compiler = "zig-cc"`). Medido: `.dev-fs/cache/zig` tenía ~11,4 M de inodos de
+ # 12,5 M usados en el fs — el 91 %. Ni `ls` terminaba ahí. Es cache puro (zig lo rehace; la
+ # primera tanda después va más lenta y ya), y NADIE lo podaba nunca: ni este watchdog, ni
+ # `poda-fuentes.sh`, ni la cosecha.
+ #
+ # ⚠ Por qué `mv` y DESPUÉS `rm -rf`, y no `rm -rf` directo: borrar millones de ficheros
+ # tarda minutos, y un build que arranque en el medio escribiría DENTRO del árbol que se está
+ # borrando. El rename es atómico: el build siguiente encuentra el sitio vacío y crea su
+ # cache nuevo mientras el viejo se va en paralelo. Mismo motivo por el que esto va dentro de
+ # la guarda de «ningún build en vuelo» que protege a las caches Go.
+ #
+ # La ruta sale igual que en `config.rs` (`cache_root = <proy>/.dev-fs/cache`), con el mismo
+ # override `CACHE` que acepta el build: si alguien lo mueve, el watchdog lo sigue.
+ zcache="${CACHE:-$HAMMER_DIR/.dev-fs/cache}/zig"
+ if [ -d "$zcache" ]; then
+ zbasura="$(dirname "$zcache")/.zig-borrando-$$"
+ if mv "$zcache" "$zbasura" 2>/dev/null; then
+ rm -rf "$zbasura" &
+ echo "$(date -u +%FT%TZ) watchdog: cache de zig apartado y borrándose en segundo plano"
+ fi
+ fi
fi
fi
done