From abda7d3cc1d1e58005eb1d214d75b333b6213a6c Mon Sep 17 00:00:00 2001 From: Sergio Date: Thu, 3 Sep 2026 20:27:05 +0000 Subject: [PATCH] contrato de kernel: alta de pidfd, y los consumidores dejan de describir un bug ya arreglado MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Handoff de vuelta desde tawasuyu (SDD 25 §8): las seis tareas del otro lado estan cerradas, y eso dejo este contrato mintiendo de dos formas. - Alta de `proceso-por-descriptor` (pidfd_open / pidfd_send_signal). Se usa desde W1 y no estaba declarada. `symbols` vacio a proposito: no depende de ningun CONFIG_*, es interfaz del core desde Linux 5.3. Al no estar en ningun perfil no se comprueba contra un .config; entra porque el contrato es la lista de lo que se USA, y si manana el minimo de kernel baja de 5.3, esta linea es la que lo dice. - Los cinco consumidores de cgroup llevaban numero de linea y quedaron viejos con W1-W4. Van por nombre de funcion, y la regla de escritura queda arriba. - Los `silent` de esos cinco describian el bug que W4 arreglo ("solo emite un warn!", "silencio total"). Un guardian que dice que hay un punto ciego donde ya no lo hay es peor que no tenerlo. - `contabilidad-por-tarea` decia que sandokan sondea /proc para medir una unidad. Desde W2 eso sale del cgroup en O(1); el que sigue sondeando /proc es el monitor de procesos del SISTEMA, que es otro consumidor. Y en SDD 25 §8, el estado real: las seis cerradas, mas W4.bis, donde el hallazgo no fue el que este documento suponia. En arje el limite no se descartaba: NO SE PEDIA. El camino `plain` -el de casi todas las Cards- no creaba cgroup, y `apply_rlimits_to_cgroup` la llamaba solo shuma. Verificado con el binario, no de memoria: `hammer kernel contract --list` carga las 14 capacidades, y `hammer kernel contract --profile anfitrion-cards` sigue en verde (11 exigidas presentes, 13 miradas). Co-Authored-By: Claude Opus 5 Claude-Session: https://claude.ai/code/session_01GiYsdSwF1nxjBTTsa1empe --- docs/25-tasas-del-kernel.md | 19 ++++++++- docs/state/kernel-contract.toml | 74 ++++++++++++++++++++++++++------- 2 files changed, 77 insertions(+), 16 deletions(-) diff --git a/docs/25-tasas-del-kernel.md b/docs/25-tasas-del-kernel.md index 3da379f1..380596d5 100644 --- a/docs/25-tasas-del-kernel.md +++ b/docs/25-tasas-del-kernel.md @@ -873,7 +873,24 @@ Su precio, concreto: ## 8. Propuestas — lado tawasuyu -Detalle en el handoff espejo. En una línea cada una: +> **✅ LAS SEIS CERRADAS (2026-09-02/03).** Detalle y evidencia en el handoff espejo del otro +> repo (`tawasuyu/HANDOFF-TASAS-KERNEL-DESDE-HAMMER.md` §3). Lo que hay que saber desde acá: +> +> - **W4 dejó tres pendientes y se cerraron como W4.bis (2026-09-03).** El grande no era lo que +> este documento suponía: en arje el límite **no se descartaba, no se pedía**. El camino +> `plain` —el de casi todas las Cards, con los namespaces en `false`— no creaba cgroup, y +> `apply_rlimits_to_cgroup` la llamaba sólo shuma, así que arje nunca escribió `memory.max` +> ni `pids.max`. Ahora hay una sola puerta (`arje-incarnate::cgroup::preparar`) y el hijo del +> camino `plain` entra al cgroup entre `fork` y `execve`. +> - **`docs/state/kernel-contract.toml` quedó al día el 2026-09-03**: alta de +> `proceso-por-descriptor` (pidfd, que se usa desde W1 y no estaba declarada), `consumer` por +> nombre de función sin número de línea, y los `silent` de los cinco cgroups reescritos — +> describían un bug ya arreglado, que es la peor forma que tiene un guardián de mentir. +> - **Lo que sigue sin evidencia**: el OOM-kill del kernel sobre un Ente de arje. Sólo se puede +> ver en una máquina con delegación de cgroups; el clon de desarrollo corre con uid 1001 sobre +> la jerarquía de root. + +En una línea cada una, como se propusieron: - **W1 · `pidfd` para liveness y señales** en `sandokan-local` (hoy `access("/proc/")` y `waitpid(WNOHANG)` en sondeo). Se gana correctitud (carrera de PID) antes que velocidad. diff --git a/docs/state/kernel-contract.toml b/docs/state/kernel-contract.toml index bdfe6f12..bbb25d24 100644 --- a/docs/state/kernel-contract.toml +++ b/docs/state/kernel-contract.toml @@ -17,6 +17,12 @@ # # REGLA DE ALTA. Una capacidad entra acá sólo si hay un consumidor con ruta y símbolo. Lo que # «estaría bueno tener» no es un contrato. +# +# REGLA DE ESCRITURA. Los `consumer` van por **nombre de función, sin número de línea**. Los +# números envejecen con cada commit del otro repo y ya lo hicieron: entre W1 y W4 (2026-09-02) +# los cinco `consumer` de cgroups quedaron apuntando a líneas movidas, y —peor— sus `silent` +# describían un bug ya arreglado («sólo emite un warn!», «silencio total»), que es la forma de +# mentir más cara que tiene un guardián: decir que hay un punto ciego donde ya no lo hay. version = 1 reviewed_against = "7.1.2" @@ -31,13 +37,17 @@ title = "Tope de memoria por unidad" symbols = ["MEMCG"] interface = ["memory.max", "memory.high", "memory.current"] consumer = """ -arje-incarnate::cgroup::{apply_rlimits_to_cgroup:78, set_memory_max_at:204, set_memory_high_at:210} \ -← sandokan-core::Engine::{set_memory_max,set_memory_high} ← sandokan-monitor-llimphi (govierna memory.high)""" +arje-incarnate::cgroup::{preparar, apply_rlimits_to_cgroup, set_memory_max_at, set_memory_high_at} \ +← sandokan-core::Engine::{set_memory_max,set_memory_high} ← sandokan-monitor-llimphi (govierna \ +memory.high) · sandokan-local::medir_entidad lee `memory.current`""" silent = """ -Sin MEMCG los ficheros NO EXISTEN. `apply_rlimits_to_cgroup` sólo emite `warn!(memory.max write \ -failed)` y sigue: la Card corre sin tope y el resto del sistema se entera cuando el OOM killer \ -elige a otro. Es el fallo de CLAUDE.md §3 — un ausente falla ruidosamente, un vacío llega hasta \ -el final diciendo que todo fue bien.""" +YA NO ES SILENCIOSO (tawasuyu, W4 el 2026-09-02 y W4.bis el 2026-09-03). Sin MEMCG los ficheros \ +NO EXISTEN, y ahora eso se dice: `apply_rlimits_to_cgroup` devuelve `Limites{aplicados, faltantes}` \ +con el símbolo que falta —y sólo acusa al kernel si pudo leer `/sys/fs/cgroup/cgroup.controllers` \ +y el controller no estaba—; arje lo marca por unidad (`Degradation::CgroupLimitNotApplied`, visible \ +en `arje-ctl status` como `corriendo⚠`) y shuma lo devuelve en las `warnings` de `create`. \ +Lo que este contrato SÍ sigue sin poder desmentir: nadie vio todavía el OOM-kill del kernel sobre \ +un Ente de arje en una máquina con delegación de cgroups.""" notes = """ PAGADO 2026-08-30 (SDD 25 §7-H1): `-e MEMCG` en la fase configure de linux-generic, linux-metal, \ linux-metal-dual y la derivada linux-gioser, en una sola tanda porque re-hashea el kernel \ @@ -49,24 +59,35 @@ id = "peso-cpu" title = "Reparto proporcional de CPU por unidad" symbols = ["CGROUPS", "CGROUP_SCHED", "FAIR_GROUP_SCHED"] interface = ["cpu.weight", "cpu.stat"] -consumer = "arje-incarnate::cgroup::set_cpu_weight_at:170 (y :60, que descarta el error)" -silent = "`let _ = write(cpu.weight)` en :60: el peso no se aplica y no queda ni un warn." +consumer = "arje-incarnate::cgroup::{preparar, ensure_cgroup, set_cpu_weight_at}" +silent = """ +YA NO ES SILENCIOSO (W4, 2026-09-02): el `let _ = write(cpu.weight)` murió. El peso que no se \ +pudo escribir vuelve al caller nombrando `CGROUP_SCHED`, y el reweight en caliente de `pacha` \ +también (W4.bis: `set_cpu_weight_at` pasa por `escribir_control`, que exige nombrar el símbolo).""" [[capability]] id = "tope-procesos" title = "Tope de procesos por unidad" symbols = ["CGROUPS", "CGROUP_PIDS"] interface = ["pids.max", "pids.current"] -consumer = "arje-incarnate::cgroup::apply_rlimits_to_cgroup:85" -silent = "Mismo patrón que memory.max: sólo un `warn!` y la unidad forkea sin límite." +consumer = """ +arje-incarnate::cgroup::{preparar, apply_rlimits_to_cgroup} · sandokan-local::medir_entidad lee \ +`pids.current`""" +silent = """ +Mismo patrón que memory.max, y arreglado con él: el tope que no se pudo escribir vuelve al caller \ +y marca la unidad. ⚠️ Hasta W4.bis (2026-09-03) había algo peor que un `warn!`: en arje el tope \ +NO SE PEDÍA. El camino `plain` —el de casi todas las Cards, con los namespaces en `false`— no \ +creaba cgroup, y `apply_rlimits_to_cgroup` la llamaba sólo shuma.""" [[capability]] id = "peso-io" title = "Reparto de E/S por unidad" symbols = ["BLK_CGROUP", "BLK_CGROUP_IOCOST"] interface = ["io.weight", "io.cost.qos", "io.cost.model"] -consumer = "arje-incarnate::cgroup::set_io_weight_at:217 (y :64, que descarta el error)" -silent = "`let _ = write(io.weight)`: silencio total." +consumer = "arje-incarnate::cgroup::{preparar, ensure_cgroup, set_io_weight_at}" +silent = """ +YA NO ES SILENCIOSO (W4, 2026-09-02): el `let _ = write(io.weight)` murió, igual que el de \ +`cpu.weight`. El peso que no se pudo escribir vuelve al caller nombrando `BLK_CGROUP`.""" notes = """ `io.weight` con sintaxis «default N» lo publica iocost, no BFQ — por eso el contrato pide \ BLK_CGROUP_IOCOST y no IOSCHED_BFQ (que está apagado en las recetas y no hace falta).""" @@ -76,7 +97,7 @@ id = "cpus-por-unidad" title = "Pinneo de CPUs por unidad" symbols = ["CPUSETS"] interface = ["cpuset.cpus", "cpuset.mems"] -consumer = "arje-incarnate::cgroup::set_cpuset_at:247" +consumer = "arje-incarnate::cgroup::set_cpuset_at" [[capability]] id = "jaula-de-rutas" @@ -140,8 +161,31 @@ title = "taskstats + proc connector (el reemplazo de sondear /proc)" symbols = ["TASKSTATS", "TASK_DELAY_ACCT", "CONNECTOR", "PROC_EVENTS"] interface = ["netlink NETLINK_CONNECTOR", "TASKSTATS_CMD_GET"] consumer = """ -sandokan-local — PREVISTO (SDD 25 T3/W2): hoy sondea `/proc/` a 3,4 µs por proceso. \ -Se declara `wants` porque el kernel ya lo trae por defconfig y perderlo sería una regresión silenciosa.""" +sandokan-local — PREVISTO (SDD 25 T3/W2). ⚠️ Ya no es cierto que sandokan sondee `/proc` para \ +medir una unidad: desde W2 (2026-09-02) la telemetría POR UNIDAD sale del cgroup en O(1) \ +(`arje-incarnate::cgroup::medir` → `memory.current`/`pids.current`/`cpu.stat`). Lo que sigue \ +sondeando `/proc` es el monitor de procesos del SISTEMA (`sysmon-core::scan`), que necesita \ +enumerar todo y es otro consumidor. Se declara `wants` porque el kernel ya lo trae por defconfig \ +y perderlo sería una regresión silenciosa.""" + +[[capability]] +id = "proceso-por-descriptor" +title = "pidfd: agarrar un proceso por descriptor y no por su número" +symbols = [] +interface = ["pidfd_open(2)", "pidfd_send_signal(2)"] +consumer = "sandokan-local::pidfd (backend opcional, `disponible()`) ← LocalEngine::{stop, reap}" +silent = """ +NINGUNO, y por eso `symbols` va vacío: pidfd no depende de ningún `CONFIG_*` — es interfaz del \ +core desde Linux 5.3, así que en un kernel de hammer va a estar siempre. Entra al contrato \ +porque el contrato es la lista de lo que se USA, no la de lo que puede faltar; si mañana el \ +mínimo de kernel baja de 5.3, esta línea es la que lo dice. `sandokan kernel` la mide con una \ +syscall de prueba (no hay `.config` que leer en el Ubuntu ajeno) y tiene tercer estado \ +`NoSeSabe`: un «no pude preguntar» informado como «no está» manda a recompilar un kernel sano.""" +notes = """ +ALTA PEDIDA DESDE TAWASUYU (2026-09-03, handoff de tasas del kernel §3, «dos cosas que le tocan \ +a hammer»). Se usa desde W1 (2026-09-02): `pidfd_open` al encarnar —único momento sin carrera, \ +porque un hijo sin cosechar no cede su número—, el descriptor vive en la `Entity`, `stop` señala \ +por ahí y la rama `ECHILD` de `reap` le pregunta al descriptor. `/proc` queda entero detrás (W5).""" # --------------------------------------------------------------------------------------------- # Perfiles: PARA QUÉ es cada kernel