tawasuyu

sergio/takana

> Una distribución Linux construida con un laboratorio funcional y hermético en el > sótano, y una terminal mutable, clásica y anárquica en el piso de arriba — diseñada > desde el suelo para que una IA programadora entienda, modifique y co…

TOML 1572Shell 203Rust 109Markdown 993728 Sutichaykuna

fix(granja): el watchdog del worker miraba los BYTES, y lo que murió fueron los INODOS

Medido el 2026-10-07 en dev.gioser.net: `df -h` decía **90% con 20 G libres** —se lee como «va
bien»— y `df -i` decía **13107200/13107200, 0 inodos libres**. El journal del servicio traía
**63.000 `No space left on device` desde el 28-sep**: el worker no podía escribir ni sus propios
logs, y estuvo así nueve días sin que nada gritara.

Peor que el fallo es cómo se veía: el loop informaba **«BUILD-YIELD: 37/57 construyen (0 fallan)»**.
Las 20 que no construyen no se cuentan como fallo **porque no puede escribir el `.fail` que las
marcaría** — el mecanismo que registra el fallo lo rompe la misma causa que el fallo. O sea un ✓
FALSO, que es peor que el ✗ falso de un disco lleno: el ✗ falso manda a leer una receta sana, el ✓
falso dice que la granja va bien durante nueve días.

Y este watchdog SÍ se despertó: purgó cachés Go puntualmente, por el % de bytes, con los inodos a
cero. No es que el umbral envejeciera — vigilaba una magnitud distinta de la que falla.

Ahora la presión es `max(% bytes, % inodos)` y se usa en los dos sitios: la ventana de post-mortem
del suelo frío y la purga de cachés Go. El log **dice cuál medidor disparó** («inodos al 97%»), que
no es cosmética: sin eso alguien purgaría cachés buscando bytes que no faltan. `INODE_HIGH` sale a
82 como `DISK_HIGH`, y `_presion` devuelve 0 sólo si `df` no contesta — un 0 inventado leería
«holgado» justo cuando no se sabe.

Comprobado contra el worker: `_presion /opt/takana` ⇒ «97 inodos» con los bytes al 84%.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>

a15025093c7e543b32ab1d1d5c840d75a484b684 · ruwaq Sergio · (14 p'unchaw ñawpaq) · tayta cb2013f54 · kay commitpi sach'ata qhaway

Semántico sayk'uy

Mana ima sutichaypas t'ikrakurqanchu: qillqalla, yachachiylla utaq willaylla t'ikrakurqan.

1 qillqakuna · +32 −5

diff --git a/scripts/farm/farm-worker-loop.sh b/scripts/farm/farm-worker-loop.sh
index c4a0d5f7..4a963ada 100755
--- a/scripts/farm/farm-worker-loop.sh
+++ b/scripts/farm/farm-worker-loop.sh
@@ -36,6 +36,31 @@ mkdir -p "$HAMMER_DIR/work"
 # pisa un vendor en curso, esa receta reintenta el próximo ciclo (cache-hit el resto). Sin esto, una
 # tanda Go grande llena el disco de 80G y el I/O-wait dispara el load (cuello real, no CPU/RAM).
 DISK_HIGH="${DISK_HIGH:-82}"
+# ⚠ Y EL MISMO UMBRAL PARA LOS INODOS, porque lo que tumbó esta caja no fueron los bytes
+# (2026-10-07, medido): `df -h` decía **90% con 20 G libres** —se lee como «va bien»— y `df -i`
+# decía **13107200/13107200, 0 libres**. El journal traía **63.000 `No space left on device` desde
+# el 28-sep**: el worker no podía escribir ni sus propios logs, y estuvo así nueve días.
+#
+# Peor que el fallo: el loop informaba «BUILD-YIELD: 37/57 construyen (0 fallan)». Las 20 que no
+# construyen no se cuentan como fallo **porque no puede escribir el `.fail` que las marcaría** — el
+# mecanismo que registra el fallo lo rompe la misma causa que el fallo, así que el síntoma es un
+# **✓ FALSO**: la granja «va bien» durante nueve días.
+#
+# Este watchdog estaba mirando el medidor equivocado: purgó cachés Go puntualmente, por el % de
+# BYTES, con los inodos a cero. No es que el umbral envejeciera: vigilaba una magnitud distinta de
+# la que falla. Ahora la presión es el MÁXIMO de las dos, y el log dice cuál disparó — si no lo
+# dijera, un día alguien purgaría cachés buscando bytes que no faltan.
+INODE_HIGH="${INODE_HIGH:-82}"
+# Presión del recurso: max(% bytes usados, % inodos usados). Un vacío devuelve vacío, no 0 — un 0
+# inventado leería «holgado» justo cuando `df` no contesta.
+_presion() {
+  local pb pi
+  pb=$(df --output=pcent  "$1" 2>/dev/null | tr -dc '0-9')
+  pi=$(df --output=ipcent "$1" 2>/dev/null | tr -dc '0-9')
+  [ -z "$pb" ] && pb=0
+  [ -z "$pi" ] && pi=0
+  if [ "$pi" -gt "$pb" ]; then echo "$pi inodos"; else echo "$pb bytes"; fi
+}
 # Cuántos minutos se le respetan a un árbol EN FRÍO cuando el disco NO está apretado. Es la ventana
 # de post-mortem: por debajo de esto, un fallo se lleva su propia evidencia (ver protección 4).
 SUELO_FRIO_MIN="${SUELO_FRIO_MIN:-120}"
@@ -74,13 +99,15 @@ SUELO_FRIO_MIN="${SUELO_FRIO_MIN:-120}"
       # No se arregla subiendo el suelo a secas: este watchdog existe para que una tanda Go no llene
       # el disco vendoreando, y ahí los minutos importan. Se arregla siendo agresivo SÓLO cuando el
       # recurso escasea de verdad — con disco holgado, un árbol frío no le hace daño a nadie.
-      usado=$(df --output=pcent "$HAMMER_DIR" 2>/dev/null | tr -dc '0-9')   # pcent = USADO, no libre
+      # Presión = max(bytes, inodos). Antes era sólo `--output=pcent` (bytes) y con los inodos a
+      # cero este suelo seguía respetando la ventana de post-mortem mientras nada podía escribir.
+      set -- $(_presion "$HAMMER_DIR"); usado="$1"; cual="$2"
       suelo=$SUELO_FRIO_MIN
-      [ -n "$usado" ] && [ "$usado" -ge "$DISK_HIGH" ] && suelo=2   # disco apretado ⇒ como antes
+      [ -n "$usado" ] && [ "$usado" -ge "$DISK_HIGH" ] && suelo=2   # recurso apretado ⇒ como antes
       [ -n "$(find "$dd" -mmin -"$suelo" -print -quit 2>/dev/null)" ] && continue
       rm -rf "$dd"
     done
-    use=$(df --output=pcent "$HAMMER_DIR" 2>/dev/null | tr -dc '0-9')
+    set -- $(_presion "$HAMMER_DIR"); use="$1"; medidor="$2"
     if [ -n "$use" ] && [ "$use" -ge "$DISK_HIGH" ]; then
       # OJO: `go clean -modcache`/`-cache` borran ~/go/pkg/mod y ~/.cache/go-build ENTEROS. Ambos los
       # usa `go mod vendor` (modcache p/ módulos, go-build p/ verificar el grafo). Si la purga corre
@@ -91,9 +118,9 @@ SUELO_FRIO_MIN="${SUELO_FRIO_MIN:-120}"
       # un `go mod vendor` visible en este instante): difiero al próximo tick / al gap idle entre
       # ciclos. El sandbox `go install` usa GOCACHE=/src + -mod=vendor ⇒ no toca estas caches del host.
       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: disco ${use}% ≥ ${DISK_HIGH}% pero hay build en vuelo ⇒ difiero purga caches Go"
+        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: disco ${use}% ≥ ${DISK_HIGH}% ⇒ purgo caches Go"
+        echo "$(date -u +%FT%TZ) watchdog: ${medidor} al ${use}% ≥ ${DISK_HIGH}% ⇒ purgo caches Go"
         go clean -modcache 2>/dev/null; go clean -cache 2>/dev/null
       fi
     fi