tasas H4: un test hace regla lo que era suerte — cero pre_exec en el workspace

pre_exec apaga el camino posix_spawn de Rust y devuelve el spawn a fork+exec: 231 µs contra
3 949 µs con 256 MB tocados en el padre (SDD 25 T1). hammer no usa ninguno hoy, pero nadie lo
comprobaba.

El test se comprobó en los dos sentidos: inyectando un pre_exec en sandbox.rs se pone rojo, y
si el barrido no encuentra fuentes también falla — un guardián que aprueba por vacío es el
fallo de CLAUDE.md §3.

Co-Authored-By: Claude Opus 5 <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_01ACUcwo9mZsE5ocYVE9npih
This commit is contained in:
Sergio
2026-08-30 01:48:37 +00:00
co-authored by Claude Opus 5
parent 292e3ed6cf
commit 697d3f21a1
2 changed files with 80 additions and 3 deletions
+73
View File
@@ -0,0 +1,73 @@
//! Guardián de SDD 25 §7-H4: **ningún camino de spawn de hammer usa `pre_exec`**.
//!
//! POR QUÉ. `std::process::Command` de Rust usa `posix_spawn` cuando puede, y `posix_spawn` es
//! PLANO respecto al tamaño del padre: a 256 MB tocados, `fork+exec` costó 3 949 µs contra 231 µs
//! (SDD 25 T1, **17×**). En cuanto se le pone un `pre_exec`, Rust apaga ese camino y cae a
//! `fork+exec`, que copia el espacio de direcciones del padre. hammer lanza builds desde un
//! proceso que puede tener cientos de MB mapeados.
//!
//! Hoy `hammer-build::sandbox` lanza `bwrap` sin `pre_exec` — y hasta ahora eso era SUERTE, no una
//! regla: nadie lo comprobaba. Esto lo vuelve una regla.
//!
//! CUÁNDO SE PUEDE APAGAR. Si algún día hace falta de verdad (un `setsid`, un `unshare` que bwrap
//! no cubra), la respuesta no es borrar el test: es escribir acá por qué ese caso paga los 17× y
//! ponerlo en la lista de excepciones, para que el precio quede a la vista de quien lo lea.
use std::path::{Path, PathBuf};
fn raiz_del_workspace() -> PathBuf {
// CARGO_MANIFEST_DIR = crates/hammer-build
PathBuf::from(env!("CARGO_MANIFEST_DIR"))
.parent()
.and_then(Path::parent)
.expect("crates/<crate> tiene dos ancestros")
.to_path_buf()
}
fn rs_de(dir: &Path, out: &mut Vec<PathBuf>) {
let Ok(rd) = std::fs::read_dir(dir) else { return };
for e in rd.flatten() {
let p = e.path();
if p.is_dir() {
rs_de(&p, out);
} else if p.extension().is_some_and(|x| x == "rs") {
out.push(p);
}
}
}
#[test]
fn ningun_spawn_de_hammer_usa_pre_exec() {
let raiz = raiz_del_workspace();
let crates = raiz.join("crates");
assert!(crates.is_dir(), "no encontré {} — ¿se movió el workspace?", crates.display());
let mut fuentes = Vec::new();
rs_de(&crates, &mut fuentes);
// Un barrido que no encuentra fuentes aprueba por vacío, que es el fallo de CLAUDE.md §3.
assert!(fuentes.len() > 20, "sólo {} fuentes: el barrido no miró nada", fuentes.len());
let mut culpables = Vec::new();
for f in &fuentes {
if f == Path::new(file!()) || f.ends_with("tests/sin_pre_exec.rs") {
continue;
}
let Ok(txt) = std::fs::read_to_string(f) else { continue };
for (i, l) in txt.lines().enumerate() {
let limpia = l.trim_start();
if limpia.starts_with("//") || limpia.starts_with("*") {
continue; // documentarlo no es usarlo
}
if l.contains("pre_exec") {
culpables.push(format!("{}:{}: {}", f.display(), i + 1, l.trim()));
}
}
}
assert!(
culpables.is_empty(),
"SDD 25 §7-H4: `pre_exec` apaga el camino `posix_spawn` de Rust y devuelve el spawn a \
`fork+exec` (17× más caro con 256 MB en el padre, SDD 25 T1). Aparece en:\n {}",
culpables.join("\n ")
);
}
+7 -3
View File
@@ -403,9 +403,13 @@ Su precio, concreto:
parcheado en la receta. Queda escrito para no rediscutirlo.
- **H3 · `harkaq` usa `openat2 RESOLVE_BENEATH|RESOLVE_NO_SYMLINKS`** donde hoy valida rutas
a mano (T9, ~gratis).
- **H4 · Auditar los `Command` de hammer que ponen `pre_exec`**: Rust apaga el camino
`posix_spawn` en cuanto hay `pre_exec`, y cae a `fork+exec` (T1). `hammer-build/sandbox.rs`
lanza `bwrap` sin `pre_exec` está bien hoy; el punto es que sea una regla, no una suerte.
- **H4 · Ningún `Command` de hammer pone `pre_exec`** Rust apaga el camino `posix_spawn` en
cuanto hay uno, y cae a `fork+exec` (T1: 17× a 256 MB tocados). `hammer-build/sandbox.rs` lanza
`bwrap` sin `pre_exec`, o sea que estaba bien **por suerte**: nadie lo comprobaba. **HECHO**: el
test `crates/hammer-build/tests/sin_pre_exec.rs` barre las fuentes del workspace y falla si
aparece uno (comprobado en los dos sentidos: inyectando un `pre_exec` el test se pone rojo, y un
barrido que no encuentra fuentes también, para que no apruebe por vacío). Si algún día hace falta
de verdad, la salida es escribir en el test por qué ese caso paga los 17×, no borrarlo.
- **H5 · La materialización de dependencias se queda en hardlink** (T10) y el experimento de
reflink (§9) alimenta SDD 18 (wawafs), donde el store como FS vivo cambiaría el cociente.
- **H6 · No `madvise(MADV_HUGEPAGE)` en el camino de build** mientras `defrag=madvise` y la