CLAUDE.md: kernel-enforced memory cap (ulimit -v) for asm testing
Other agents share this machine. An OOM crash takes their state down too, not just mine. The earlier "trap + pgrep verify" pattern depended on me not forgetting; two crashes proved that's not enough. Adds a mandatory `ulimit -v 524288` (512 MB virt) to the pattern alongside the existing timeout + trap + verify. The kernel kills any rogue process at the cap, so even if all three earlier layers fail simultaneously, shared RAM is untouched. Verified: an asm process in a runaway (cons n ...) loop SIGKILL'd by the kernel within ~1 second when capped at 512 MB, before timeout 5 fired. Also noted: - ulimit only affects the current shell + children, so it cannot degrade other agents. - Use `pkill -u "$USER"` explicitly — don't pkill foreign processes. - Prefer foreground runs with timeout over backgrounding when feasible; the .lsp can exit on its own via *max-requests*.
This commit is contained in:
parent
0f894734a2
commit
fb41b73274
1 changed files with 36 additions and 26 deletions
62
CLAUDE.md
62
CLAUDE.md
|
|
@ -70,41 +70,51 @@ and failed to verify it was gone before moving on. The first crash
|
|||
added this discipline section; the second proved the discipline
|
||||
needed teeth. Hence the MANDATORY checklist below.
|
||||
|
||||
**MANDATORY pattern for any asm server test in a shell block:**
|
||||
**Shared-machine context:** other agents run on this box. An OOM
|
||||
crash is not just "my problem" — it takes their state down too.
|
||||
The rules below are belt, suspenders, AND a parachute so that even
|
||||
if two safeguards fail, the kernel itself backstops.
|
||||
|
||||
**MANDATORY pattern — every asm/uncommonlisp test in a shell block:**
|
||||
|
||||
```bash
|
||||
# 1. set -e so any failure aborts before leaving a zombie
|
||||
set -e
|
||||
# 2. trap ALWAYS fires, even if a later command fails or the script is killed
|
||||
trap 'pkill -9 -u "$USER" -f "examples/http-server|asm/uncommonlisp.*bench" 2>/dev/null || true' EXIT INT TERM
|
||||
# 3. wrap the server in `timeout` so even a total trap failure has a hard ceiling
|
||||
timeout 30 asm/uncommonlisp < server.lsp &
|
||||
set -e # (1) fail-fast
|
||||
ulimit -v 524288 # (2) KERNEL CAP: 512 MB virt
|
||||
# process gets SIGKILL at cap, no matter what
|
||||
trap 'pkill -9 -u "$USER" -f "examples/http-server|asm/uncommonlisp" 2>/dev/null || true' \
|
||||
EXIT INT TERM # (3) cleanup always fires
|
||||
|
||||
timeout 30 asm/uncommonlisp < server.lsp & # (4) wall-clock ceiling
|
||||
SPID=$!
|
||||
# 4. do the work ... (client requests, RSS measurements, etc.)
|
||||
# 5. explicit kill + wait, not just "assume the trap handles it"
|
||||
kill -9 $SPID 2>/dev/null; wait $SPID 2>/dev/null
|
||||
# 6. VERIFY before ending the block
|
||||
pgrep -u "$USER" -f 'asm/uncommonlisp|examples/http-server' && exit 1 || true
|
||||
# ... do the work (curl requests, measurements, etc.) ...
|
||||
kill -9 $SPID 2>/dev/null; wait $SPID 2>/dev/null # (5) explicit cleanup
|
||||
|
||||
# (6) VERIFY the block is clean before moving on
|
||||
pgrep -u "$USER" -f 'asm/uncommonlisp|examples/http-server' \
|
||||
&& { echo "STRAGGLER"; exit 1; } || true
|
||||
```
|
||||
|
||||
Six layers. Bypass any one and the next still catches. Two crashes
|
||||
happened when I had only layers 3-5; the kernel cap (2) is what turns
|
||||
"if I forget" from "fox reboots" into "my one rogue process dies at
|
||||
512 MB without touching shared RAM."
|
||||
|
||||
Additional rules:
|
||||
|
||||
- Never start a bg asm process without `timeout N` wrapping it, even
|
||||
for a "quick test." The mental overhead is zero; the consequence
|
||||
of forgetting is a crashed machine.
|
||||
- Bound iterations **inside the .lsp** (e.g., `(if (> n 1000) (exit 0)
|
||||
(loop (+ n 1)))`) — never rely on external kill signals alone.
|
||||
- After any block that spawned background processes, run:
|
||||
`pgrep -u "$USER" -f 'asm/uncommonlisp|examples/http-server'`
|
||||
and expect NO output. If output, kill with `pkill -9 -f ...` and
|
||||
investigate. Do not move on.
|
||||
- Use `examples/http-server.lsp`'s built-in `*max-requests* = 50000`
|
||||
ceiling (never comment out, never raise for long-running tests).
|
||||
- Python + C have real GCs; the hazard is asm-specific. But treat
|
||||
backgrounded Python + C servers with the same hygiene — they
|
||||
block ports and confuse later tests.
|
||||
- `ulimit -v` affects only the shell it runs in and its children, so
|
||||
it cannot degrade anyone else's agents. Always set it before
|
||||
backgrounding any uncommonlisp process.
|
||||
- Bound iterations **inside the .lsp** (e.g. `*max-requests* = 50000`
|
||||
in `examples/http-server.lsp`). Never raise for long-running tests.
|
||||
- Use `pkill -u "$USER" -f <pattern>` not `pkill` alone — others may
|
||||
have their own processes on this machine.
|
||||
- Prefer FOREGROUND runs when possible: `timeout 10 asm/uncommonlisp
|
||||
< test.lsp` with the `.lsp` exiting on its own beats backgrounding.
|
||||
- C has Boehm GC via `GC_MALLOC`. Python has Python's GC. Asm has
|
||||
neither. Risk scales with how long the asm process lives.
|
||||
- If `ulimit` is unavailable (some container setups), use
|
||||
`systemd-run --user --scope -p MemoryMax=512M -- asm/uncommonlisp ...`
|
||||
as the equivalent cgroup-based cap.
|
||||
|
||||
## MOAD Scanner
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue