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:
@@ -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 ")
|
||||
);
|
||||
}
|
||||
@@ -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
|
||||
|
||||
Reference in New Issue
Block a user