UNPKG

openclaw

Version:

Multi-channel AI gateway with extensible messaging integrations

90 lines (89 loc) 2.91 kB
import fs from "node:fs"; //#region src/process/linux-oom-score.ts /** * On Linux, children spawned by a long-lived parent (e.g., the gateway) inherit * the parent's `oom_score_adj`. Under cgroup memory pressure the kernel tends * to pick the largest-RSS process as the OOM victim, which is usually the * gateway rather than its transient workers. See issue #70404. * * Since Linux 2.6.20 any unprivileged process may voluntarily *raise* its own * `oom_score_adj` without `CAP_SYS_RESOURCE`. We exploit that by wrapping the * child argv in a tiny `/bin/sh` shim that raises the score in the post-fork * child and then `exec`s the real command, so there is no extra long-lived * shell process and no change to the final process identity. * * Opt out per-process by setting `OPENCLAW_CHILD_OOM_SCORE_ADJ=0` (also * accepts `false`/`no`/`off`). Callers may also provide the key via * `params.env` for per-child overrides. */ const CHILD_OOM_SCORE_ADJ_ENV_KEY = "OPENCLAW_CHILD_OOM_SCORE_ADJ"; const OOM_SCORE_WRAP_SHELL = "/bin/sh"; const OOM_SCORE_WRAP_SCRIPT = "echo 1000 > /proc/self/oom_score_adj 2>/dev/null; exec \"$0\" \"$@\""; const SHELL_INIT_ENV_KEYS = [ "BASH_ENV", "ENV", "CDPATH" ]; function isDisabled(value) { switch (value?.trim().toLowerCase()) { case "0": case "false": case "no": case "off": return true; default: return false; } } let cachedShellAvailable = null; function defaultShellAvailable() { if (cachedShellAvailable !== null) return cachedShellAvailable; try { cachedShellAvailable = fs.statSync(OOM_SCORE_WRAP_SHELL).isFile(); } catch { cachedShellAvailable = false; } return cachedShellAvailable; } function shouldWrapChildForOomScore(options) { if ((options?.platform ?? process.platform) !== "linux") return false; if (isDisabled((options?.env ?? process.env)[CHILD_OOM_SCORE_ADJ_ENV_KEY])) return false; return (options?.shellAvailable ?? defaultShellAvailable)(); } function isWrapped(command, args) { return command === OOM_SCORE_WRAP_SHELL && args[0] === "-c" && args[1] === OOM_SCORE_WRAP_SCRIPT; } function canUseShellExecCommand(command) { return !command.startsWith("-"); } function hardenShellEnv(baseEnv) { const next = { ...baseEnv ?? process.env }; for (const key of SHELL_INIT_ENV_KEYS) delete next[key]; return next; } function prepareOomScoreAdjustedSpawn(command, args = [], options) { const copy = [...args]; if (!command || !canUseShellExecCommand(command) || !shouldWrapChildForOomScore(options)) return { command, args: copy, env: options?.env, wrapped: false }; if (isWrapped(command, copy)) return { command, args: copy, env: hardenShellEnv(options?.env), wrapped: true }; return { command: OOM_SCORE_WRAP_SHELL, args: [ "-c", OOM_SCORE_WRAP_SCRIPT, command, ...copy ], env: hardenShellEnv(options?.env), wrapped: true }; } //#endregion export { prepareOomScoreAdjustedSpawn as t };