From 1728a77a3ae6e102ec913322325c55892da95bc9 Mon Sep 17 00:00:00 2001 From: Sergio Date: Mon, 14 Sep 2026 22:57:47 +0000 Subject: [PATCH] =?UTF-8?q?go/cgo:=20GOTMPDIR=20fuera=20del=20m=C3=B3dulo?= =?UTF-8?q?=20=E2=80=94=20era=20la=20=C3=BAnica=20causa=20del=20no-determi?= =?UTF-8?q?nismo=20de=20gocryptfs?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `gocryptfs` era el último no-determinismo vigente del corpus. Ya REPRODUCE. ── La medición, que primero hice mal ────────────────────────────────────────────────── Comparé los dos ficheros de `store/.divergen/` y salían 1.874.136 bytes distintos, con `.text` entre ellos y funciones de OpenSSL cambiando de AVX2 a AVX-512. Era el par EQUIVOCADO: `.divergen/` es el artefacto VIEJO archivado, no una reconstrucción. El par que define el veredicto es `.r1` (1ª reconstrucción) contra el que queda en el store (2ª). Comparado bien, el no-determinismo son **7 bytes, todos en `.debug_str`**: los dígitos de `go-build598798091` contra `go-build270591055`. (Lo de AVX no era ruido: era DERIVA real contra el artefacto viejo, sellado antes de que 467a87cd pusiera `-mcpu=baseline` en el CC de cgo. Se trata aparte, abajo.) ── La causa ─────────────────────────────────────────────────────────────────────────── `-trimpath` reescribe rutas, pero cuál gana depende de dónde esté el work dir de Go: dentro del módulo gana la reescritura del módulo y el componente ALEATORIO sobrevive; fuera, Go reescribe el work dir entero a `/tmp/go-build` y el número desaparece. El driver ponía `GOTMPDIR=/src/.gotmp`, que es justo dentro del módulo. Con CGO off da igual —no hay fuentes C generadas, ninguna ruta del work dir entra en el DWARF— y por eso las 15 recetas Go puras verificadas reproducen. Con cgo sí entra. Por eso el cambio va acotado a `cgo = true`: las otras 358 no lo necesitan. ── Medido en los dos sentidos, con un módulo cgo de juguete y cachés limpias ───────── · GOTMPDIR dentro ⇒ `go-build3806217352` en el binario; dos builds DIFIEREN · GOTMPDIR fuera ⇒ ninguna cadena `go-build`; dos builds IDÉNTICOS Y sobre gocryptfs de verdad: DERIVA primero (las dos reconstrucciones ya coincidían entre sí y el guardado era el viejo), REPRODUCE en la pasada siguiente. En el binario sólo queda `/tmp/go-build`, el marcador constante de trimpath. ── Va a `/cache` y no a `/tmp`, y se COMPRUEBA que exista ───────────────────────────── El sandbox monta `--tmpfs /tmp` (~½ RAM) y un proyecto Go grande lo desborda: ésa era la razón de poner esto en `/src`. `/cache` es un bind RW a disco (el de la caché de zig), así que conserva el disco y queda fuera del módulo. Y no se da por hecho que esté: con `--tmp-overlay /` la raíz es escribible, así que un `mkdir -p /cache/gotmp` a ciegas TRIUNFARÍA creando el directorio en la capa tmpfs —en RAM, el desbordamiento que se quería evitar, y en silencio. Si el bind falta, el build muere diciéndolo. --- crates/takana-build/src/lib.rs | 38 +++++++++++++++++++++++++++++++-- docs/state/repro-verificado.tsv | 3 +++ 2 files changed, 39 insertions(+), 2 deletions(-) diff --git a/crates/takana-build/src/lib.rs b/crates/takana-build/src/lib.rs index 8802c535..87326911 100644 --- a/crates/takana-build/src/lib.rs +++ b/crates/takana-build/src/lib.rs @@ -984,10 +984,44 @@ fn resolve_phases(recipe: &Recipe, src: &Path) -> takana_core::Result { } else { ("CGO_ENABLED=0", "-ldflags=-buildid=") }; + // ── GOTMPDIR: DENTRO del módulo con cgo apagado, FUERA con cgo encendido ────── + // `-trimpath` reescribe rutas, pero cuál gana depende de dónde esté el work dir: + // · si cae DENTRO del módulo, gana la reescritura del módulo y el componente + // ALEATORIO `go-build` SOBREVIVE; + // · si cae FUERA, Go reescribe el work dir entero a `/tmp/go-build` y el número + // desaparece. + // Con CGO off da igual: no hay fuentes C generadas, así que ninguna ruta del work + // dir entra en el DWARF — las 15 recetas Go puras verificadas REPRODUCEN. Con cgo + // sí entra, y ésa era la ÚNICA causa del no-determinismo de `gocryptfs`: dos + // reconstrucciones difieren en SIETE bytes, todos en `.debug_str`, y son los + // dígitos de `go-build598798091` contra `go-build270591055`. + // + // Medido con un módulo cgo de juguete, cachés limpias y en los dos sentidos + // (2026-09-14): + // · GOTMPDIR dentro ⇒ `go-build3806217352` aparece en el binario; dos builds DIFIEREN + // · GOTMPDIR fuera ⇒ ninguna cadena `go-build`; dos builds IDÉNTICOS + // + // ⚠ Va a `/cache`, NO a `/tmp`: el sandbox monta `--tmpfs /tmp` (~½ RAM) y un + // proyecto Go grande lo desborda con sus objetos — es la razón por la que esto + // estaba en `/src` de entrada. `/cache` es un bind RW a disco (el mismo que usa la + // caché de zig), así que conserva la propiedad de estar en disco Y queda fuera del + // módulo. + // + // ⚠ Y se COMPRUEBA que `/cache` exista en vez de dar por hecho que está: con + // `--tmp-overlay /` la raíz es escribible, así que un `mkdir -p /cache/gotmp` a + // ciegas TRIUNFARÍA creando el directorio en la capa tmpfs — o sea en RAM, que es + // exactamente el desbordamiento que se quería evitar, y en silencio. Si no está, + // el build muere diciendo por qué. + let gotmp_setup = if recipe.build.cgo { + "[ -d /cache ] || { echo '!! falta el bind /cache: GOTMPDIR caería en la capa tmpfs (RAM) y un proyecto Go grande la desborda' >&2; exit 1; }; mkdir -p /cache/gotmp" + } else { + "mkdir -p /src/.gotmp" + }; + let gotmpdir = if recipe.build.cgo { "/cache/gotmp" } else { "/src/.gotmp" }; Some(format!( - "{cd_prefix}export GOCACHE=/src/.gocache GOTMPDIR=/src/.gotmp GOPATH=/tmp/gopath \ + "{cd_prefix}export GOCACHE=/src/.gocache GOTMPDIR={gotmpdir} GOPATH=/tmp/gopath \ GOTOOLCHAIN=local {cgo_env} GOFLAGS=-mod=vendor GOPROXY=off \ - GOBIN=/out/usr/bin; mkdir -p /src/.gotmp; \ + GOBIN=/out/usr/bin; {gotmp_setup}; \ go install -trimpath {ldflags} {pkg}" )) } diff --git a/docs/state/repro-verificado.tsv b/docs/state/repro-verificado.tsv index 188f9b19..1dcbd0ab 100644 --- a/docs/state/repro-verificado.tsv +++ b/docs/state/repro-verificado.tsv @@ -236,3 +236,6 @@ libplasma 73ec5fb279ea44e6bcfdf4c58d8703ed1307ae7985e56ce55a5da078ea098b7e 2026- syntax-highlighting 3db73c19bdbddcb046a80d11ec112bc7dc60134eeec75b26e907cc756d25d081 2026-09-14 reproduce powerdevil 70ea4b373af5ac3c22cf6adde9ca45153925724ef523000bc1e0e2338e282d15 2026-09-14 reproduce ktexteditor f13baeb75249c7f768fdd1b4584b5077779e6806ffcb295a6554f50d3f31d0ec 2026-09-14 reproduce +gocryptfs 0caf96352eae21ed51b32be9ead8d5525bb07e8aa124eb33c4b9588f1a02421d 2026-09-14 NO-DETERMINISMO +gocryptfs 0caf96352eae21ed51b32be9ead8d5525bb07e8aa124eb33c4b9588f1a02421d 2026-09-14 reproduce (tras poner al día un artefacto derivado) +gocryptfs 0caf96352eae21ed51b32be9ead8d5525bb07e8aa124eb33c4b9588f1a02421d 2026-09-14 reproduce