fix(throttle): use /proc/meminfo MemAvailable on Linux (#556)

`getMemoryUsage()` in src/core/backoff.ts computes 1 - freemem()/totalmem(),
where Node's `os.freemem()` returns Linux's `MemFree`. `MemFree` excludes the
page cache, which the kernel grows aggressively in any environment that reads
files (i.e. essentially all containers). On a healthy 4 GB Linux container with
~1.4 GB MemAvailable, MemFree is routinely ~100 MB, so `getMemoryUsage()`
reports 96-97% used and `waitForCapacity()` rejects every job with:

  Throttle timeout: system overloaded after 20 attempts (~600s).
  Load: ..%, Memory: 97%

even though the host has plenty of usable memory.

Linux exposes `MemAvailable` in `/proc/meminfo` precisely as the kernel's
estimate of memory available for new allocations without swapping (it factors
in reclaimable page cache). This is what `htop` and `free -h` show as
"available". Using it removes the false positive entirely.

Behaviour:
- On Linux (when /proc/meminfo is readable): use 1 - MemAvailable/MemTotal.
- Anywhere else (macOS, Windows, sandboxed envs without /proc): unchanged
  fallback to 1 - freemem()/totalmem().

Scope is intentionally minimal — data correctness only. An env override like
GBRAIN_MEMORY_STOP_PCT would also be reasonable but is out of scope here.

Co-authored-by: Kevin Hsu <kevinhsu.ecofirst@gmail.com>
This commit is contained in:
chengzehsu
2026-07-23 13:24:46 -07:00
committed by GitHub
co-authored by Kevin Hsu
parent 38f446bb6f
commit b78fc56e98
+14
View File
@@ -81,6 +81,20 @@ function getLoad(): number {
/** Get memory usage fraction (0-1) */
function getMemoryUsage(): number {
// Prefer /proc/meminfo MemAvailable on Linux — os.freemem() returns
// MemFree which excludes page cache, falsely reading "high pressure"
// in any container where the kernel caches files.
try {
// eslint-disable-next-line @typescript-eslint/no-var-requires
const fs = require('fs');
const meminfo: string = fs.readFileSync('/proc/meminfo', 'utf8');
const totalKb = Number(meminfo.match(/MemTotal:\s+(\d+)/)?.[1]);
const availKb = Number(meminfo.match(/MemAvailable:\s+(\d+)/)?.[1]);
if (totalKb > 0 && availKb >= 0) return 1 - availKb / totalKb;
} catch {
/* fall through to os.freemem() (non-Linux or /proc unavailable) */
}
// Non-Linux fallback (macOS, Windows, or any host without /proc/meminfo)
const total = totalmem();
if (total === 0) return 0;
return 1 - (freemem() / total);