Files
AgentHub/scripts/server/pane-launch.sh
T
nntrivi2001 705353b0da fix(server): a member's test run can no longer freeze the box
On 11-02 open sessions plus test runs filled RAM and the 4 GB swap on a
slow disk; the box hung until a hard reset. Member panes now start at
nice 10, IO priority 7 and oom_score_adj 500 with Vitest capped at 2
workers by default, and the system unit caps its cgroup (server + every
pane) at 85% of RAM with 1G swap and OOMPolicy=continue, so the kernel
kills the biggest pane process instead of hanging the machine. Needs
`agenthub server install-system` once.
2026-11-03 10:27:58 +07:00

43 lines
2.3 KiB
Bash

#!/bin/bash
# @file pane-launch.sh
# @description Server mode, one Linux user per account (S3): installed as
# /opt/agenthub/libexec/pane-launch. A member's pane starts as
# `pane-launch <launch file>`: the dashboard creates the session AS the member
# through sudo, and sudo logs its whole command line — so the pane's
# environment (a pane token, a pool account's token) and its argv (the prompt)
# are not in that command line but in the launch file, which `write-as`
# wrote as the member (0600) from stdin. This reads it, deletes it, exports
# the environment and execs the command; no shell ever parses a value.
#
# The file is NUL-separated: the number of environment entries, the
# NAME=value entries, then the argv (none = a login shell). Every pane starts
# at a lower CPU/IO priority and as the kernel's first OOM pick (see below).
# @author Nguyễn Ngọc Trí Vĩ <vinnt@smartgift.vn>
set -eu
f=${1:?usage: pane-launch <launch file>}
mapfile -d '' -t parts <"$f"
rm -f -- "$f"
n=${parts[0]}
[[ "$n" =~ ^[0-9]+$ ]] || { echo "pane-launch: malformed launch file" >&2; exit 2; }
for kv in "${parts[@]:1:n}"; do
[[ "$kv" =~ ^[A-Za-z_][A-Za-z0-9_]*= ]] || { echo "pane-launch: bad variable" >&2; exit 2; }
export "$kv"
done
# A member's pane shares the 4-core box with the dashboard and every other
# member: its test run, build or dev server must slow down before anything
# else does, and die first if memory runs out (11-02: 36 sessions plus test
# runs filled RAM and swap, and the box froze until a hard reset). A member
# cannot take the nice back (that needs root); oom_score_adj they can lower
# only to the unit's own -100, and even then the biggest process dies. The unit
# caps the whole cgroup below RAM (install-system.sh), so the kernel's pick
# inside it is the biggest pane process, never the dashboard or tmux.
renice -n 10 -p $$ >/dev/null 2>&1 || true
ionice -c 2 -n 7 -p $$ >/dev/null 2>&1 || true
echo 500 >/proc/self/oom_score_adj 2>/dev/null || true
# Vitest ≤ 3 starts one worker per core by default and these override the
# config; a member may set their own in the pane.
export VITEST_MAX_THREADS="${VITEST_MAX_THREADS:-2}" VITEST_MAX_FORKS="${VITEST_MAX_FORKS:-2}"
argv=("${parts[@]:n+1}")
if [ ${#argv[@]} -eq 0 ]; then exec -l "${SHELL:-/bin/bash}"; fi
exec "${argv[@]}"