Cap run.sh run at 2G RAM and 2 cores
Some checks are pending
md-naming / markdown naming convention (push) Waiting to run
pre-commit / pre-commit (push) Waiting to run
runnability / runnability (push) Waiting to run

Running the courses made this machine stutter during a game. That was me:
PORR's suite solves systems up to 10000x10000 and takes every core it is
given, and the pip installs are not cheap either.

A promise to be careful is not a fix, so `run` now executes inside a transient
systemd scope -- MemoryMax=2G, MemorySwapMax=0 (OOM-kill rather than thrash
swap), CPUQuota=200%, nice -n 19, ionice -c 3. Same mechanism the pre-push
hook in testsAndMisc uses. Verified from inside the scope: memory.max reads
2147483648 and cpu.max reads "200000 100000", so the kernel is enforcing it
rather than the script hoping.

RUN_MEMORY_MAX and RUN_CPU_QUOTA override it for a single deliberate run.

Recorded the two courses that still fail: PORR, which will now be OOM-killed
under the cap and needs a smaller default parameter set, and ZPOB, where
latexmk exits 12 from bibtex despite producing the PDF.

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Claude-Session: https://claude.ai/code/session_0172dj34FDpao6wsix8ZuCzE
This commit is contained in:
Krzysztof Rudnicki 2026-08-28 19:05:24 +02:00
parent a8d873b4fd
commit d76e571400
3 changed files with 44 additions and 2 deletions

View File

@ -33,8 +33,16 @@ Work directly on `main`; commit and push straight there.
./install.sh # deps + pre-commit hooks, then verifies and fails loudly
./run.sh check # the full local gate
./run.sh status # probe every course dir, regenerate DOCS-runnability.md
./run.sh run <dir> # build/run one course, capped at 2G RAM and 2 cores
```
`run` executes inside a transient systemd scope (`MemoryMax=2G`,
`MemorySwapMax=0`, `CPUQuota=200%`, `nice -n 19`, `ionice -c 3`), so a course
build cannot make an interactive machine stutter. This is not caution, it is a
kernel-enforced ceiling -- PORR's suite solves 10000x10000 systems and will
take every core it is given. Raise it deliberately for a single run with
`RUN_MEMORY_MAX=8G RUN_CPU_QUOTA=800% ./run.sh run <dir>`.
Never `--no-verify`. If a gate fails, fix the cause.
## Development Workflow

View File

@ -70,6 +70,16 @@ tests exist** -- splitting untested code is the highest-risk action here.
will touch hundreds of submitted coursework files, so they want their own
reviewed commit rather than being buried in a config change.
### 6. Two courses still fail `run`
- `Programming/PORR` -- its pytest suite solves systems up to 10000x10000 and
runs for many minutes at full CPU. Under the 2G/2-core cap it will be
OOM-killed rather than finish. It needs either a smaller default parameter
set for `run`, or to be marked as a deliberately heavy target.
- `Programming/ZPOB` -- latexmk exits 12 from bibtex even though `esej.pdf` is
produced. Probably a missing/!stale .bib entry; needs a clean-room rebuild
to see the first real error rather than the cached one.
## Standing rule
A gate lands only once the thing it gates is clean, so

28
run.sh
View File

@ -22,6 +22,16 @@ readonly SCRIPT_DIR
readonly RUNNABILITY_DOC="${SCRIPT_DIR}/DOCS-runnability.md"
# Exit codes a per-directory run.sh uses to report itself.
# Resource ceiling for `run`. A course build is background work and must never
# be what makes an interactive machine stutter -- PORR's suite solves
# 10000x10000 systems and happily eats every core it is given.
#
# Same mechanism as the pre-push hook in testsAndMisc: a transient systemd
# scope, so the limit is enforced by the kernel rather than by good intentions.
# MemorySwapMax=0 means it gets OOM-killed instead of thrashing swap.
readonly RUN_MEMORY_MAX="${RUN_MEMORY_MAX:-2G}"
readonly RUN_CPU_QUOTA="${RUN_CPU_QUOTA:-200%}"
readonly EXIT_BLOCKED=78
readonly EXIT_NO_CODE=79
@ -142,8 +152,22 @@ cmd_run() {
[[ -n "$target" ]] || { err "run needs a directory"; usage 1; }
local runner="${SCRIPT_DIR}/${target}/run.sh"
[[ -x "$runner" ]] || { err "no executable run.sh in $target"; return 1; }
log "running $target"
"$runner"
log "running $target (capped at ${RUN_MEMORY_MAX} RAM, ${RUN_CPU_QUOTA} CPU)"
_run_capped "$runner"
}
# Run a command under a memory/CPU cap, niced so it yields to anything
# interactive. Falls back to plain nice if systemd-run is unavailable.
_run_capped() {
if command -v systemd-run > /dev/null 2>&1; then
systemd-run --user --scope --quiet \
-p "MemoryMax=${RUN_MEMORY_MAX}" \
-p MemorySwapMax=0 \
-p "CPUQuota=${RUN_CPU_QUOTA}" \
nice -n 19 ionice -c 3 "$@"
else
nice -n 19 ionice -c 3 "$@"
fi
}
cmd_sync_vendored() {