granja: el lock ya no se lo puede quedar un proceso fugado
El lock lo sostiene la descripción de fichero abierta y los hijos la heredan: con 'exec 9>' +
'flock 9' cualquier descendiente que sobreviva al script retiene el lock para siempre, y el
siguiente que lo pida espera sin que nada falle. Medido hoy: dos firefox colgados de una caza
sobrevivieron al kill de su bwrap y dejaron a la granja hora y media sin poder compilar.
Dos formas de defecto ⇒ dos arreglos, y no son intercambiables:
· cosecha-cron, campana-deuda, harvest-harkaq — el lock cubre el script ENTERO, así que se
re-ejecutan bajo 'flock -o' (cierra el fd antes de ejecutar). Ir poniendo '9>&-' comando a
comando ahí es jugar a los topos. '-E 77' separa 'estaba ocupado' de 'el trabajo falló',
que con el 9> no se distinguían.
· farm-worker-loop, latido — el lock vive en un subshell / es a propósito el mecanismo de
vida, así que basta '9>&-' en los hijos. En latido NO se puede usar -o: ahí el fd retenido
ES como --status sabe que el latido vive sin pidfile. Su modo de fallo era el peor de
todos: un nieto fugado dejaba el latido MUERTO PERO APARENTANDO ESTAR VIVO.
Medido antes de elegir, no deducido del manual:
exec 9> + flock 9 → el nieto RETIENE (control negativo: reproduce el fallo)
exec {L}> (fd auto) → el nieto RETIENE (bash NO lo marca close-on-exec)
flock -o / 9>&- hijo → lock LIBRE
Y probados los cinco sobre el fichero real, no sobre una maqueta: copias truncadas justo tras
el bloque del lock, invocadas con RUTA RELATIVA DESDE OTRO DIRECTORIO (que es lo que rompe un
$0 sin resolver — de hecho la primera versión de harvest-harkaq calculaba YO después del cd y
habría fallado ahí). Los tres entran, ninguno deja el lock tomado por el nieto, y la exclusión
mutua sigue funcionando con su mensaje y su exit 0 — que es lo que un arreglo de locks puede
romper en silencio.
Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_014QPJteswQvP1L7zSBrQQe2
This commit is contained in:
@@ -26,6 +26,9 @@
|
||||
# ssh root@<ip> 'journalctl -u campana-deuda -f'
|
||||
set -u
|
||||
HAMMER_DIR="${HAMMER_DIR:-/opt/hammer}"
|
||||
# Ruta absoluta a MÍ MISMO antes del `cd`: acá el `cd` va a HAMMER_DIR, que ni siquiera tiene por
|
||||
# qué contener a este script, así que un `$0` relativo se pierde seguro.
|
||||
YO="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
|
||||
cd "$HAMMER_DIR"
|
||||
HAMMER="${HAMMER:-$HAMMER_DIR/target/release/hammer}"
|
||||
STORE="${STORE:-$HAMMER_DIR/store}"
|
||||
@@ -63,10 +66,27 @@ ts() { date -u +%FT%TZ; }
|
||||
LOCK_FARM="${LOCK_FARM:-$HAMMER_DIR/work/.farm-build.lock}"
|
||||
LOCK_WAIT="${LOCK_WAIT:-7200}"
|
||||
mkdir -p "$(dirname "$LOCK_FARM")"
|
||||
exec 9>"$LOCK_FARM"
|
||||
if ! flock -w "$LOCK_WAIT" 9; then
|
||||
echo "$(ts) campaña-deuda: el worker-loop no soltó el lock en ${LOCK_WAIT}s ⇒ salgo sin construir" | tee -a "$LOG"
|
||||
exit 0
|
||||
# ⚠ EL LOCK LO SOSTIENE EL fd, Y LOS HIJOS LO HEREDAN (medido 2026-09-08). Con `exec 9>` + `flock 9`
|
||||
# cualquier descendiente que se fugue retiene el lock DESPUÉS de que el script termine, y el próximo
|
||||
# que lo pida espera para siempre sin que nada falle. Pasó: dos `firefox` colgados de una caza de
|
||||
# bugs sobrevivieron al `kill` de su `bwrap` y dejaron a la granja hora y media sin poder compilar,
|
||||
# en silencio. `fuser -v <lock>` es lo que lo delata.
|
||||
# El arreglo es `flock -o`, que cierra el fd antes de ejecutar ⇒ el lock lo sostiene SÓLO el
|
||||
# proceso `flock`, y ningún nieto puede prolongarlo. Como acá la sección crítica es el script
|
||||
# ENTERO, se re-ejecuta bajo `flock -o` en vez de ir poniendo `9>&-` comando por comando, que es
|
||||
# jugar a los topos y se olvida uno. `-E 77` distingue «el lock estaba ocupado» de «el trabajo
|
||||
# falló», que con el `9>` de antes no se podían separar.
|
||||
# Comprobado con control positivo y negativo: sin `-o` el nieto retiene; con `-o` no. Y que la
|
||||
# exclusión mutua SIGUE funcionando, que es lo que un arreglo de locks puede romper sin avisar.
|
||||
# ⚠ Ojo: `exec {L}>` (fd automático de bash) NO sirve — bash no lo marca close-on-exec. Medido.
|
||||
if [ -z "${CAMPANA_DEUDA_CON_LOCK:-}" ]; then
|
||||
export CAMPANA_DEUDA_CON_LOCK=1
|
||||
rc=0; flock -w "$LOCK_WAIT" -E 77 -o "$LOCK_FARM" "$YO" "$@" || rc=$?
|
||||
if [ "$rc" = 77 ]; then
|
||||
echo "$(ts) campaña-deuda: el worker-loop no soltó el lock en ${LOCK_WAIT}s ⇒ salgo sin construir" | tee -a "$LOG"
|
||||
exit 0
|
||||
fi
|
||||
exit "$rc"
|
||||
fi
|
||||
echo "$(ts) campaña-deuda: lock de build tomado (el worker-loop espera su turno)" | tee -a "$LOG"
|
||||
total=$(echo "$DEUDA" | wc -w); i=0; ok=0; fail=0; falladas=""; ultimo_rc=0
|
||||
|
||||
@@ -25,6 +25,9 @@
|
||||
set -uo pipefail
|
||||
|
||||
ROOT="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
# Ruta absoluta a MÍ MISMO, capturada ANTES del `cd`: se usa para re-ejecutarse bajo el lock, y
|
||||
# un `$0` relativo dejaría de resolver en cuanto cambiamos de directorio.
|
||||
YO="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
|
||||
cd "$ROOT"
|
||||
|
||||
# LOCK (2026-07-22): el latido ya no viene sólo del crontab — `latido.sh` lo lanza desde la sesión
|
||||
@@ -35,10 +38,27 @@ cd "$ROOT"
|
||||
# -n = no esperar: si ya hay un ciclo corriendo, este sobra (el próximo tick recoge lo mismo).
|
||||
LOCKFILE="${LOCKFILE:-$ROOT/work/.cosecha-cron.lock}"
|
||||
mkdir -p "$(dirname "$LOCKFILE")"
|
||||
exec 9>"$LOCKFILE"
|
||||
if ! flock -n 9; then
|
||||
echo "── $(date -u +%FT%TZ) cosecha-cron: ya hay un ciclo en curso ⇒ salgo (no me solapo)"
|
||||
exit 0
|
||||
# ⚠ EL LOCK LO SOSTIENE EL fd, Y LOS HIJOS LO HEREDAN (medido 2026-09-08). Con `exec 9>` + `flock 9`
|
||||
# cualquier descendiente que se fugue retiene el lock DESPUÉS de que el script termine, y el próximo
|
||||
# que lo pida espera para siempre sin que nada falle. Pasó: dos `firefox` colgados de una caza de
|
||||
# bugs sobrevivieron al `kill` de su `bwrap` y dejaron a la granja hora y media sin poder compilar,
|
||||
# en silencio. `fuser -v <lock>` es lo que lo delata.
|
||||
# El arreglo es `flock -o`, que cierra el fd antes de ejecutar ⇒ el lock lo sostiene SÓLO el
|
||||
# proceso `flock`, y ningún nieto puede prolongarlo. Como acá la sección crítica es el script
|
||||
# ENTERO, se re-ejecuta bajo `flock -o` en vez de ir poniendo `9>&-` comando por comando, que es
|
||||
# jugar a los topos y se olvida uno. `-E 77` distingue «el lock estaba ocupado» de «el trabajo
|
||||
# falló», que con el `9>` de antes no se podían separar.
|
||||
# Comprobado con control positivo y negativo: sin `-o` el nieto retiene; con `-o` no. Y que la
|
||||
# exclusión mutua SIGUE funcionando, que es lo que un arreglo de locks puede romper sin avisar.
|
||||
# ⚠ Ojo: `exec {L}>` (fd automático de bash) NO sirve — bash no lo marca close-on-exec. Medido.
|
||||
if [ -z "${COSECHA_CRON_CON_LOCK:-}" ]; then
|
||||
export COSECHA_CRON_CON_LOCK=1
|
||||
rc=0; flock -n -E 77 -o "$LOCKFILE" "$YO" "$@" || rc=$?
|
||||
if [ "$rc" = 77 ]; then
|
||||
echo "── $(date -u +%FT%TZ) cosecha-cron: ya hay un ciclo en curso ⇒ salgo (no me solapo)"
|
||||
exit 0
|
||||
fi
|
||||
exit "$rc"
|
||||
fi
|
||||
|
||||
SSH_KEY="${SSH_KEY:-$HOME/.ssh/github5}"
|
||||
|
||||
@@ -230,7 +230,11 @@ while :; do
|
||||
flock 9
|
||||
echo "$(date -u +%FT%TZ) lock tomado, sigo con $Q"
|
||||
fi
|
||||
JOBS="$JOBS" PROMOTE=0 QUEUE="$Q" scripts/build-farm.sh 2>&1 | tail -50
|
||||
# `9>&-` cierra el fd DEL LOCK en el hijo: si un build deja un proceso fugado, sin esto el
|
||||
# nieto retiene el lock para siempre y la granja queda muda (medido 2026-09-08; ver la
|
||||
# regla 1 de CLAUDE.md). Acá el lock vive en el subshell `( … ) 9>`, así que alcanza con
|
||||
# cerrarlo en los hijos y no hace falta re-ejecutar nada.
|
||||
JOBS="$JOBS" PROMOTE=0 QUEUE="$Q" scripts/build-farm.sh 2>&1 9>&- | tail -50 9>&-
|
||||
) 9>"$LOCK_FARM"
|
||||
done
|
||||
if [ "$total" -gt 0 ]; then
|
||||
|
||||
@@ -24,6 +24,9 @@
|
||||
set -u
|
||||
|
||||
HUB="$(cd "$(dirname "$0")/../.." && pwd)"
|
||||
# Ruta absoluta a MÍ MISMO capturada ANTES del `cd`: se usa para re-ejecutarse bajo el lock, y un
|
||||
# `$0` relativo dejaría de resolver en cuanto cambiamos de directorio.
|
||||
YO="$(cd "$(dirname "$0")" && pwd)/$(basename "$0")"
|
||||
cd "$HUB"
|
||||
WORKER="${1:?uso: harvest-harkaq.sh root@<ip>}"
|
||||
|
||||
@@ -31,10 +34,19 @@ WORKER="${1:?uso: harvest-harkaq.sh root@<ip>}"
|
||||
# corrupto. El harvest es lento (re-indexa el store por veredicto), así que un ciclo puede pisar
|
||||
# al siguiente. flock con -n: si ya hay una corriendo, esta sale limpia en vez de solaparse.
|
||||
LOCK="$HUB/work/.harvest-harkaq.lock"
|
||||
exec 9>"$LOCK"
|
||||
if ! flock -n 9; then
|
||||
echo "$(date -u +%FT%TZ) otra cosecha en curso, salto este ciclo"
|
||||
exit 0
|
||||
# ⚠ `flock -o` y re-exec, NO `exec 9>` (medido 2026-09-08): el lock lo sostiene el fd y los hijos
|
||||
# lo heredan, así que un `ssh` o un `rsync` fugado de esta cosecha lo retendría DESPUÉS de que el
|
||||
# script termine y todas las cosechas siguientes saldrían por «otra cosecha en curso» — mudas y
|
||||
# para siempre. Con `-o` el fd se cierra antes de ejecutar y sólo el proceso `flock` lo sostiene.
|
||||
# `-E 77` separa «estaba ocupado» de «el trabajo falló». Ver la regla 1 de CLAUDE.md.
|
||||
if [ -z "${HARVEST_HARKAQ_CON_LOCK:-}" ]; then
|
||||
export HARVEST_HARKAQ_CON_LOCK=1
|
||||
rc=0; flock -n -E 77 -o "$LOCK" "$YO" "$@" || rc=$?
|
||||
if [ "$rc" = 77 ]; then
|
||||
echo "$(date -u +%FT%TZ) otra cosecha en curso, salto este ciclo"
|
||||
exit 0
|
||||
fi
|
||||
exit "$rc"
|
||||
fi
|
||||
SSH_KEY="${SSH_KEY:-$HOME/.ssh/github5}"
|
||||
REMOTE="${REMOTE:-/opt/hammer}"
|
||||
|
||||
+10
-1
@@ -88,7 +88,16 @@ trap 'echo "── $(ts) latido termina (pid $$)" >>"$LOG"' EXIT
|
||||
while :; do
|
||||
# `|| true` + `set -uo pipefail` sin `-e`: un ciclo que falla (worker caído, red, gitea) NO puede
|
||||
# matar el latido. Se loguea y se reintenta al próximo tick; ésa es toda la robustez que hace falta.
|
||||
( cd "$ROOT" && ./scripts/farm/cosecha-cron.sh ) >>"$COSECHA_LOG" 2>&1 || \
|
||||
# `9>&-` cierra el fd DEL LOCK DEL LATIDO en el hijo. Sin esto, un proceso fugado de la cosecha
|
||||
# lo retiene para siempre y el latido queda MUERTO PERO APARENTANDO ESTAR VIVO: `--status` lee
|
||||
# el lock, lo ve tomado y dice VIVO, y `--ensure` se niega a levantar otro. Es el peor modo de
|
||||
# fallo posible acá — la máquina deja de latir y todo indica que late. Medido 2026-09-08 con
|
||||
# dos `firefox` colgados que retuvieron el lock de build hora y media (regla 1 de CLAUDE.md).
|
||||
# ⚠ Y acá NO va `flock -o`, al revés que en cosecha-cron/campana-deuda/harvest: en este script
|
||||
# el fd retenido ES el mecanismo de vida a propósito (así `--status` responde sin pidfile, que
|
||||
# mentiría si el proceso muriera). Lo que sobra es que lo hereden los HIJOS, no que lo tenga el
|
||||
# bucle. Por eso el arreglo es distinto aunque el defecto sea el mismo.
|
||||
( cd "$ROOT" && ./scripts/farm/cosecha-cron.sh ) 9>&- >>"$COSECHA_LOG" 2>&1 || \
|
||||
echo "── $(ts) latido: el ciclo salió != 0 (ver cosecha-cron.log) — sigo" >>"$LOG"
|
||||
sleep "$INTERVALO"
|
||||
done
|
||||
|
||||
Reference in New Issue
Block a user