Repository navigation
Memory-aware worker starts: wait while the shared machine's memory is over the ceiling - #218
Merged
Merged
Conversation
…hared machine's memory is over the supervisor's machine_memory_ceiling Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
… resumes, a fresh resume is noted, unreadable files never throw, docs) Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Member
Author
|
Reviewed at d8246c6:
CI passes 9 of 9. Merging. This is the fix for today's three memory restarts on the shared box. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
What
A new worker or planner doesn't start while the shared machine's memory is at or over the supervisor's
machine_memory_ceiling.machineMemory()returns{ inUse, max }in bytes. It reads the cgroup v2 cap first (memory.max,memory.current, andmemory.stat'sfilelessshmem, throughDEVTEAM_CGROUP). Without a cap it uses/proc/meminfo(MemTotal less MemAvailable, throughDEVTEAM_MEMINFO). With neither (macOS), or when a file can't be read, it returns null and blocks nothing.machineLinenow uses it, so there's one copy of the code.worker/planner: a new start is refused with, for example, "the machine's memory is at 27.2 of 32.0 GB (85%), at or over its 80% ceiling: start this worker when some sessions finish, or pass --force (it is logged)".--forcestarts it anyway, and theworker-slotsact records what was overridden. A resume of a session that already ran is never refused. That includes a planner whose plan branch is only on origin, and the accounts-tick relaunches.--resumeon a session that never ran counts as a new start. A resume that has to start fresh because its worktree is gone notes the memory in its act.machine_memory_ceiling: default 0.8, valid from 0.5 to 0.95. It is not inOWNER_SETTINGS.validatereports an invalid value as an error, and the check uses 0.8 in its place, so no value can make the ceiling looser than the allowed range.idle-capacityandidle-planningare suppressed and amemory-fullfinding takes their place. It says how full the memory is and suggests loweringmax_active_tickets.process/supervisor.md§4/§5: the machine is shared; a start refused for memory means waiting, not--force; a supervisor that keeps hitting the ceiling lowers its ownmax_active_tickets, without asking the owner.test/lib/cli.mjs,test/lib/kit.mjsand the CI job env point both paths nowhere unless a test sets its own.Why
Several supervisors share one Render box (32 GB cgroup, 4 CPUs). Each one starts workers up to its own
max_active_tickets, and nothing checks the machine as a whole. Today the box hit its memory limit and the container restarted three times. The last time, Website had about 8 Claude sessions plus annpm cirunning, alongside the other fleets.Tested
test/memory-ceiling.test.mjsuses a fake cgroup directory. It covers: refused over the ceiling (worker, planner, and--resumeon a session that never ran);--forceoverrides and is logged; a real resume over the ceiling is allowed; a resume that starts fresh is allowed and noted in its act; allowed under the ceiling; the supervisor's own ceiling (0.9) is used; invalid values (0.99, 1, 2, 0.4, "0.9", null) failvalidateand fall back to 0.8; shmem counts as in use; the/proc/meminfofallback; no cgroup and no meminfo means allowed and nomemory-fullfinding;idle-capacityis suppressed andmemory-fullis shown, andidle-capacitycomes back under the ceiling.test/machine.test.mjsgains a check thatmachine statusreads meminfo.What Fable found (adversarial review)
Fixed:
memory.stat'sfileincludes shmem (tmpfs, /dev/shm), which the kernel can't drop, so in-use memory was undercounted. Now shmem is subtracted fromfile.--resume. It now counts as having run.readTextcan throw on EACCES/EIO.machineMemorynow returns null in that case and doesn't crash.memory-fullrow; "a resume always starts" now says a session that never ran isn't a resume; "at or over" is the accurate wording at the boundary.test/lib/kit.mjsnow sets the two env vars at module scope.Not fixed, left for follow-up:
DEVTEAM_CGROUPandDEVTEAM_MEMINFOpointed at nothing skip the check, and no act records it. This gives no more power than--forcealready does, but it leaves no trace./proc/meminfo. This is noted in the code comment.🤖 Generated with Claude Code