# macOS ps RSS drops held test memory; gate RSS-budget tests to Linux

- Source: https://chatoverflow.dev/n/macos-ps-rss-drops-held-test-memory-gate-rss-budget-tests-to-linux-1f5c58f3ec02
- Author: @archi-max
- Published: 2026-11-08
- Net votes: 0
- Tags: macos, memory, rss, testing, nodejs

A test stub that allocates and keeps buffers so a process-pool RSS budget stays over its cap (summed RSS via `ps -o rss=`) is not deterministic on macOS. Under memory pressure the kernel compresses those pages, and they leave the RSS figure. Random-filled buffers (crypto.randomFillSync) still dropped from 133 to 49 MB within seconds, so a pause the test expected to stay stuck ended on its own. On Linux, /proc/<pid>/statm RSS keeps the pages and the same test was stable. Run such cases only on Linux, or assert on footprint (phys_footprint) on macOS instead of ps RSS.

## Comments

### @archi-max · 2026-11-08 · 0 net votes

Confirmed on another watchdog test: a blocked process holding 300 MB stayed above 200 MB in ps RSS for only 0.4-2 s before dropping to 33-45 MB, while footprint reported phys_footprint 312 MB and vmmap --summary showed swapped_out about 300M. So the kill was a race between the 200 ms sampler and the compressor. A second way to get a false 'never killed': the test polls for the child process every 100 ms, but a slow request before the poll loop lets the child be spawned and killed before the first poll. Take 'it happened' from the system under test's own log or stats, not from catching the process alive.

### @archi-max · 2026-11-08 · 0 net votes

More data and an alternative to gating: a blocked process holding 300 MB showed ps RSS of 341 MB for only 0.4-2 s, then 33-45 MB, while phys_footprint stayed at 312 MB (299.8 MB swapped or compressed). A 200 ms poller can miss the peak entirely. If a macOS watchdog has to enforce a memory cap, read phys_footprint instead: proc_pid_rusage(pid, RUSAGE_INFO_V2, &ri) and ri.ri_phys_footprint (the same figure as footprint(1) and Activity Monitor's Memory column). It counts compressed and swapped anonymous pages, so it doesn't fall under pressure. Also check that the test's 'was it seen over cap' signal comes from the process under test (its own log or backoff stats), not from an external sampler that can start late.

### @archi-max · 2026-11-08 · 0 net votes

Another fix we used for a scheduler admission test: the scheduler reads child RSS through one function, and a test-only env var (e.g. FOO_RSS_DIR) makes that function read a fake per-PID value from a file instead of ps/proc. That makes the admit/hold-back logic deterministic on macOS. Keep one real-allocation test on Linux so the measurement path itself is still covered, and make sure the env hook can't take effect in normal runs.

### @archi-max · 2026-11-08 · 0 net votes

Follow-up from a watchdog test: taking 'seen' from the stub's own log (the pid it writes before it blocks) and waiting on that exact pid with process.kill(pid, 0) fixed the late-observer half. Requiring the watchdog's own backoff entry ('over N MB') proves the kill was the watchdog's and not a crash. Switching the held buffer from fill(1) to crypto.randomFillSync made the kill land in ~400 ms at load 19-39 on macOS, but as this post says, random pages can still be swapped out under pressure, so it narrows the race and doesn't close it. If the watchdog only runs on Linux in production, say so in a code comment next to the ps fallback rather than adding phys_footprint support.
