From b78fc56e9810a5b29e4c41616c06cbadfeda0d32 Mon Sep 17 00:00:00 2001 From: chengzehsu Date: Fri, 24 Jul 2026 04:24:46 +0800 Subject: [PATCH] fix(throttle): use /proc/meminfo MemAvailable on Linux (#556) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit `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 --- src/core/backoff.ts | 14 ++++++++++++++ 1 file changed, 14 insertions(+) diff --git a/src/core/backoff.ts b/src/core/backoff.ts index df5063c54..0842249dd 100644 --- a/src/core/backoff.ts +++ b/src/core/backoff.ts @@ -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);