Runbook: RB19 — Resource exhaustion (TasksMax / MemoryMax / fd)¶
Symptom¶
- Engine fails to spawn threads / coroutines / subprocesses; journal:
OSError: [Errno 11] Resource temporarily unavailableorRuntimeError: can't start new thread. - Engine fails to open files / sockets; journal:
OSError: [Errno 24] Too many open files. - systemd journal:
service: Killing process N (...) with signal SIGKILLattributed toTasksMaxorMemoryMax. /readyzflaps or fails because the engine cannot accept new connections.- See ./oom_loop.md if the symptom is repeated restarts due to MemoryMax. This runbook is for in-place exhaustion diagnosis and unit-level cap adjustments.
Detection¶
maid_engine_open_fdsnear the unit'sLimitNOFILE.maid_engine_thread_countnearTasksMax.maid_engine_rss_bytesapproachingMemoryHigh/MemoryMax.- systemd journal:
Reached MemoryMax limitortasks-max reached. - Doctor:
maid-admin doctor --phase runtime --instance <inst>reportsresource_headroom: warnorfail.
Blast radius¶
- Players affected: ALL on this instance — new sessions fail; existing sessions may hang on resource-bound operations.
- Data at risk: in-flight writes if the engine is killed by systemd before draining.
- AI/external systems: AI dialogue spawns extra coroutines; AI load amplifies thread/fd usage.
Prerequisites¶
- Interim
maidCLI (MAID_SRC): there is no system-widemaidbinary until M1.4 (install.shleaves/opt/maid/venvempty), so the CLI runs from a source checkout.export MAID_SRC=/path/to/maid-checkoutand build its venv once:sudo -u maid-engine -H uv sync --frozen --project "${MAID_SRC}"(uv:/opt/maid/current/.uv/bin/uv). Commands below run"${MAID_SRC}/.venv/bin/maid"as themaid-engineuser; insidebash -cblocksMAID_SRCis forwarded via--preserve-env=MAID_SRC. - Tools:
maid-admin,systemctl,journalctl,cat /proc/<PID>/status,ls /proc/<PID>/fd,ss -tnp. - Access: root, or an equivalently broad
sudogrant — this runbook runssudo systemctl/sudo journalctlon the instance units, which the narrowmaid-adminpriv-helper allowlist (packaging/sudoers/maid-admin.template) does not grant (it only covers fixedmaid-admin <verb>calls). There is nomaid-opsgroup in the packaging — only themaid-admingroup plus themaid-engineservice user (the restore/cleanup units run asroot). - Env file:
/etc/maid/<inst>.env. - Paths to know:
- Unit drop-in dir:
/etc/systemd/system/maid-engine@.service.d/ - Engine PID:
cat /run/maid-engine/<inst>/engine.pid - Audit access:
tail -f /var/log/maid/ops-audit.jsonl. - Escalation: see ./escalation-contacts.md.template.
First 5 minutes (LITERAL commands)¶
# 1. Find the engine PID and current resource counters at the cgroup level
sudo systemctl status maid-engine@<inst>.service --no-pager
sudo systemctl show maid-engine@<inst>.service \
--property=MainPID,MemoryCurrent,MemoryHigh,MemoryMax,TasksCurrent,TasksMax,IOPressure,CPUUsageNSec
# 2. Per-process resource view
PID=$(systemctl show -p MainPID --value maid-engine@<inst>.service)
echo "engine PID=$PID"
sudo cat /proc/"$PID"/status \
| grep -E 'Vm(Peak|RSS|Size|HWM)|Threads|FDSize'
# 3. Open file descriptors right now
sudo ls /proc/"$PID"/fd | wc -l
sudo cat /proc/"$PID"/limits | grep -E 'open files|processes'
# 4. Current connection load
sudo ss -tnp | grep -c "pid=$PID"
# 5. Capture state for post-incident
sudo journalctl -u maid-engine@<inst>.service -n 300 \
> ./incident-resource-$(date -u +%Y%m%dT%H%M%SZ).log
Investigation¶
Step 1: which resource is exhausted?¶
Compare TasksCurrent vs TasksMax, MemoryCurrent vs MemoryMax,
and ls /proc/<PID>/fd | wc -l vs LimitNOFILE from
/proc/<PID>/limits. Whichever is closest to its ceiling is the cap
you need to bump or whose leak you need to fix.
Step 2: thread / task exhaustion¶
# How many threads does the engine have? Where are they?
sudo cat /proc/"$PID"/status | grep '^Threads'
# Per-thread (top-level), see what they're doing
sudo ls /proc/"$PID"/task | wc -l
sudo cat /proc/"$PID"/task/*/comm 2>/dev/null | sort | uniq -c | sort -rn | head
asyncio.create_task in a hot path → file ticket.
- Subprocess spawning runaway (rare in MAID; check off-tick LLM queue).
- ThreadPool not capped.
Step 3: fd exhaustion¶
# What KIND of fd is dominating?
sudo ls -l /proc/"$PID"/fd \
| awk '{print $11}' | sed 's|/[0-9]\+$||' | sort | uniq -c | sort -rn | head
socket: count high → connection leak (sessions not being closed) or
AI provider HTTP client leak. Check ss -tnp for connections to
AI provider endpoints in CLOSE_WAIT.
- anon_inode:[eventpoll] high → epoll leak (asyncio loops not being
cleaned up).
- regular files high → check the engine's log/file handle usage.
Step 4: memory exhaustion¶
See ./oom_loop.md for the heap-dump procedure
(py-spy dump). For non-OOM-killed memory pressure (cgroup
MemoryHigh throttling without an outright kill), the same procedure
applies; capture a snapshot first, then decide bump-vs-fix.
Mitigation¶
Immediate: shed load¶
# Trip the AI kill switch (functional; largest single source of bursty
# threads/fds). `maid ops ai disable` is an M9 stub — use kill-switch.
sudo -u maid-engine "${MAID_SRC}/.venv/bin/maid" ops kill-switch trip "resource exhaustion" --confirm --instance <inst>
# `maid ops drain` (graceful no-new-logins drain) is an M9 stub with no
# functional equivalent yet. Tripping the AI kill switch above sheds the
# dominant thread/fd load; if that is insufficient, stop the engine cleanly
# with `sudo systemctl stop maid-engine@<inst>.service` (final save on SIGTERM).
Bump the caps (override drop-in)¶
Create a drop-in so the override survives package upgrades:
sudo mkdir -p /etc/systemd/system/maid-engine@.service.d
sudo tee /etc/systemd/system/maid-engine@.service.d/95-resource-override.conf <<'EOF'
[Service]
# Memory
MemoryMax=4G
MemoryHigh=3.5G
# Tasks (threads + processes)
TasksMax=4096
# File descriptors
LimitNOFILE=65535
EOF
# Apply
sudo systemctl daemon-reload
sudo systemctl restart maid-engine@<inst>.service
# Verify the new caps are in effect
sudo systemctl show maid-engine@<inst>.service \
--property=MemoryMax,MemoryHigh,TasksMax,LimitNOFILE
Bumping caps is mitigation, not fix. If usage was 95 % of the old cap, it will be 95 % of the new cap soon. File a follow-up.
If the engine is wedged and unresponsive¶
- Capture state first (
/proc/<PID>/status, journal). - Restart the unit; systemd takes care of teardown:
Recovery¶
MemoryCurrent/TasksCurrentplateau below their respective caps for ≥ 10 min:ls /proc/<PID>/fd | wc -lstable.maid-admin doctor --phase runtime --instance <inst>resource_headroom: pass.
- Re-enable AI if cause was not AI-side:
maid ops kill-switch reset --instance <inst>. - No
MAID_*alert active.
Post-incident¶
- File ticket with: which resource ran out, current vs cap at the moment of incident, suspect leak source, did bumping caps fix it or did a real leak need fixing.
- If override drop-in was added, file follow-up to revert it once the leak is fixed.
- Add a metric & alert for the resource that exhausted if one is missing.
- Update this runbook if a new resource class showed up.
Escalation¶
- Solo path: shed load → diagnose which resource → bump cap as mitigation → file leak ticket.
- Hosting console URL: see ./escalation-contacts.md.template.
- DNS registrar URL: see ./escalation-contacts.md.template.
- Comms channel URL: see ./escalation-contacts.md.template.
- Peer operator: see ./escalation-contacts.md.template.
- If bumping caps does not stabilize and the host headroom is insufficient to bump further, this is a leak that needs a code fix or a release rollback per ./rollback.md.