From d7d9cf8fd2484e1092caa3267bcf88195d109205 Mon Sep 17 00:00:00 2001 From: Sergio Date: Sat, 5 Sep 2026 20:05:33 +0000 Subject: [PATCH] =?UTF-8?q?repro:=20=C2=ABla=202=C2=AA=20reconstrucci?= =?UTF-8?q?=C3=B3n=20no=20construy=C3=B3=C2=BB=20NO=20es=20no-determinismo?= =?UTF-8?q?=20=E2=80=94=20mi=20propio=20bug,=20y=20lo=20pag=C3=B3=20el=20l?= =?UTF-8?q?ibro?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Corriendo una tanda con `MAX_SECONDS=45` se llenó el disco a mitad, los rebuilds empezaron a morir por ENOSPC, y la rama que yo mismo había escrito para distinguir DERIVA de NO-DETERMINISMO metió «falló» y «difiere» en la misma condición ⇒ **21 recetas sanas quedaron anotadas como NO-DETERMINISMO** en un libro que pretende ser autoritativo. Un registro falso y persistente es peor que no tener registro: el propósito del libro es que nadie tenga que volver a mirar, así que una entrada equivocada se cree para siempre. Es la trampa de siempre —un disco lleno se lee como receta rota, y la pista es que mueren varias seguidas— y el script ORIGINAL ya la evitaba: «no es lo mismo «no reproduce» que «no construye acá». Distinguirlos importa: mezclarlos inventaría un problema de determinismo». Lo rompí al añadir la segunda reconstrucción. Restituido: si la 2ª no construye, se restaura el artefacto, se cuenta como fallo y **no se anota nada** — sin veredicto es un estado legítimo. Reparado además el daño: las 21 entradas falsas purgadas del libro (quedan 27 verificaciones buenas), y `angle-grinder` repuesto desde `store/.divergen/`, donde lo había dejado la clasificación errónea. Eso sí funcionó como debía: el ejemplar estaba guardado y entero, no perdido. Y queda anotado en el propio script el límite del filtro por tiempo que provocó todo: el sidecar de `store/.times/` lo escribe el WORKER, así que acota el build de esa receta EN LA MÁQUINA QUE LO MIDIÓ. Una receta Rust de 40 s allá con la caché de cargo caliente son muchos minutos acá en frío —`angle-grinder` y `amp` entraron por el filtro y se comieron el disco— y encima el número no cuenta la CASCADA de deps que falten en el store local, que es la misma sorpresa que dio `gjs`. Con disco justo conviene pasar las recetas a mano. --- docs/state/repro-verificado.tsv | 18 +++++++++++ scripts/verificar-repro.sh | 57 +++++++++++++++++++++++++++++++-- 2 files changed, 73 insertions(+), 2 deletions(-) diff --git a/docs/state/repro-verificado.tsv b/docs/state/repro-verificado.tsv index ef797f07..a6c48df7 100644 --- a/docs/state/repro-verificado.tsv +++ b/docs/state/repro-verificado.tsv @@ -17,3 +17,21 @@ npth e5682fca87a9f591d533855b0056a07b5724304fb53402c940524d885908d980 2026-09-05 poppler-render-check 0d29dc69792df003726252fbc381a0133df7654daafccf547f8d5a82e526c1fe 2026-09-05 reproduce when 5b4048a23584aaaca41a7d8c0e50a92c469b1b1710f5d10b2dd7abd908fe57b6 2026-09-05 reproduce (tras poner al día un artefacto derivado) doas 28bc42c24008f73d9541f407562cd4ed81e3788b95ab72fa00b5369ad606064a 2026-09-05 reproduce +zstd 1ceb6215a5a3061ac385e036da642341da61a585abe62a1c904bd0c8c2f4d5fb 2026-09-05 reproduce +actionlint 12e0640aa73729d170d992eb442bcca6b9d7b891a074d2d8b9f6583710717be3 2026-09-05 reproduce +addlicense e918fed592bc9eac2989262f030dba05e4dbdb32ab9ff715abe1f05bf9e688d3 2026-09-05 reproduce +adwaita-cursors 22ae0ff6369e6f15cb6810ddd979d95ebe38307e8da668c1210d9434ef9b5b05 2026-09-05 reproduce +adwaita-hello 09cf170c6a780bd09f46347c61aa48a16eccda81e27aee42184938242b026918 2026-09-05 reproduce +age b8ae28a4e360b61aa53667af7c6261951085285e62c2201f615173731ca36716 2026-09-05 reproduce +agora-cli e05c41f1d4e46974195fbfb76a3efb591217e6733be8e17b007ba5d9cc3a153d 2026-09-05 reproduce +air 3761c6a516ab0bbcae825625e10c0fbd8015a15f25080e3b7dc36bfb7597d59f 2026-09-05 reproduce +ali 9017cf46ef5dc33c726a2fe4adf91c7463bee749ce605a94757eeea51f260c06 2026-09-05 reproduce +zoxide 83359c57babdd2e051b7c53bd63694d319969cd9bcb5a27922707655f7b572af 2026-09-05 reproduce +alsa-lib 73fb200aae06b0743035dc9f961121fc94295e80a12d39dc358920d698b7bfe0 2026-09-05 reproduce +alterx 415465a247da8489bfec54eac84d8bb2234e822423ba717974cbcbedfeb8905d 2026-09-05 reproduce +amass 385b4a140f8e8fa04282494f01e7523625aff42ae90f6eb38cd0230e6914aa56 2026-09-05 reproduce +amfora 10560630b5d8302979b7844a22d2e30696a046c834968bd713efa7fcbc2b993a 2026-09-05 reproduce +amp 7b800350e79dda56eb1c3fca511700761cd91b2cd49ebf58f453fb4a01a62944 2026-09-05 reproduce (tras poner al día un artefacto derivado) +anew 68ef0da2c17b474bee65caec2f61e4b24ccd5ea8e2d8357540ce572482bb5782 2026-09-05 reproduce +npth e5682fca87a9f591d533855b0056a07b5724304fb53402c940524d885908d980 2026-09-05 reproduce +musl-fts b9681a5205197d51bb777efde1a271e9b189bf35d74091834c5f1aa43d82f16e 2026-09-05 reproduce diff --git a/scripts/verificar-repro.sh b/scripts/verificar-repro.sh index 40097162..ea629f3a 100755 --- a/scripts/verificar-repro.sh +++ b/scripts/verificar-repro.sh @@ -143,6 +143,47 @@ else # Muestra por CLASE de build: si sólo se verifican paquetes C con meson, el resultado no dice nada # de Rust ni de Go, que son justamente donde el no-determinismo suele vivir (codegen paralelo, # orden de símbolos, timestamps de módulos). + # ── `MAX_SECONDS`: sortear sólo entre builds BARATOS ───────────────────────────────────────── + # Elegir muestra «por artefacto chico» es una trampa: el artefacto de `nodejs` pesa 0,2 MB y su + # build son horas de V8. Lo que hace cara una verificación es el TIEMPO DE CONSTRUIR, y ese dato + # ya existe — `scripts/farm/build-timed.sh` deja un sidecar por build en `store/.times/` y llega + # con la cosecha. Con `MAX_SECONDS=60` la muestra sale de ahí. + # + # Las recetas SIN medición quedan fuera cuando se pone el tope: no se puede prometer que algo es + # barato sin haberlo medido nunca. El modo de ampliar esa lista no es relajar el filtro sino + # correr builds instrumentados en el worker, que es donde el tiempo se mide. + # + # ⚠ DOS LÍMITES DEL NÚMERO, medidos el 2026-09-05 con `MAX_SECONDS=45` en el hub: + # · El sidecar lo escribe el WORKER. Una receta Rust que allá tarda 40 s con la caché de cargo + # caliente puede tardar muchos minutos acá en frío — `angle-grinder` y `amp` entraron por el + # filtro y se comieron el disco. El tope acota el build DE ESA RECETA en LA MÁQUINA QUE LO + # MIDIÓ, no acá. + # · Y no cuenta la CASCADA: `hammer build` reconstruye las deps que falten en el store local, y + # eso no está en el tiempo del sidecar. Es la misma sorpresa que dio `gjs`, cuyo grafo decía + # «una receta» y se puso a rehacer spidermonkey entero. + # ⇒ con disco justo, mejor pasar las recetas a mano que confiar en el tope. + if [ -n "${MAX_SECONDS:-}" ]; then + TODOS=$(python3 - "$MAX_SECONDS" <<'PY' +import glob, json, os, sys, re +from statistics import median +cap = float(sys.argv[1]) +m = {} +for f in glob.glob("store/.times/*.json"): + try: d = json.load(open(f)) + except Exception: continue + if d.get("name") and isinstance(d.get("seconds"), (int, float)): + m.setdefault(d["name"], []).append(d["seconds"]) +baratas = {k for k, v in m.items() if median(v) < cap} +for ruta in glob.glob("recipes/*.toml") + glob.glob("recipes/incoming-wlr/*.toml"): + txt = open(ruta, encoding="utf8", errors="replace").read(4000) + mm = re.search(r'^name\s*=\s*"([^"]+)"', txt, re.M) + if mm and mm.group(1) in baratas: + print(ruta) +PY +) + [ -n "$TODOS" ] || { echo "── no hay recetas medidas por debajo de ${MAX_SECONDS}s (¿falta store/.times?)"; exit 0; } + fi + # Se sortea entre lo que NO está en el libro, para que corridas sucesivas ACUMULEN cobertura en # vez de repetir las mismas. Con `TODO=1` se sortea entre todas (para re-verificar a propósito). TODOS=$(ls recipes/*.toml recipes/incoming-wlr/*.toml 2>/dev/null) @@ -187,8 +228,20 @@ for f in $RECETAS; do # que estaba fuera de fecha era el guardado ⇒ deriva. Si no coinciden, es no-determinismo de # verdad. Sólo se paga el rebuild extra cuando ya hubo una diferencia. mv "$STORE/$D" "$GUARDA/$D.r1"; PENDIENTE="$PENDIENTE $D.r1" - if "$HAMMER" --store "$STORE" build "$f" >/dev/null 2>&1 && [ -d "$STORE/$D" ] \ - && "$HAMMER" why-differs "$GUARDA/$D.r1" "$STORE/$D" >/dev/null 2>&1; then + # ⚠ «la segunda reconstrucción FALLÓ» NO es «difiere». Meterlas en la misma rama fue un error + # mío del 2026-09-05 y lo pagó el libro: se llenó el disco a mitad de una tanda, los rebuilds + # empezaron a morir por ENOSPC, y quedaron anotadas 21 recetas sanas como NO-DETERMINISMO — + # un registro falso y persistente, que es peor que no tener registro. Es la misma trampa de + # siempre: un disco lleno se lee como receta rota, y la pista es que mueren varias seguidas. + # El script original ya distinguía «no reproduce» de «no construye acá»; esto lo restituye. + if ! "$HAMMER" --store "$STORE" build "$f" >/dev/null 2>&1 || [ ! -d "$STORE/$D" ]; then + printf " ⚠ %-26s la 2ª reconstrucción no construyó ⇒ SIN VEREDICTO (no lo anoto)\n" "$n" + fallo=$((fallo+1)) + rm -rf "$STORE/$D"; mv "$GUARDA/$D" "$STORE/$D"; rm -rf "$GUARDA/$D.r1" + PENDIENTE=$(echo "$PENDIENTE" | sed "s| $D.r1||;s| $D||") + continue + fi + if "$HAMMER" why-differs "$GUARDA/$D.r1" "$STORE/$D" >/dev/null 2>&1; then printf " ~ %-26s DERIVA (el guardado era viejo; dos reconstrucciones coinciden)\n" "$n" deriva=$((deriva+1)) # Se anota igual: las dos reconstrucciones coincidieron entre sí, que es justo lo que