From 2015d93d2a7bc35ad7e427ad276500b2f8b3441f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Miguel=20=C3=81ngel?= Date: Mon, 25 May 2026 19:08:21 +0000 Subject: [PATCH] fix(engine): scale Chrome memory budget and frame cache to available RAM On low-memory systems (<4GB free), Chrome's --force-gpu-mem-available-mb=4096 causes the renderer to allocate more GPU texture memory than the system can provide, leading to OOM crashes during frame capture ("Target closed"). Changes: - Scale --force-gpu-mem-available-mb to match available system RAM (512MB when <2GB free, 1024MB when <4GB, 4096MB otherwise) - Add --js-flags=--max-old-space-size=N on low-memory systems to cap Chrome's V8 heap (256MB when <2GB free, 512MB when <4GB) - Scale frame data URI cache defaults: 32 entries/128MB when <2GB free, 64 entries/256MB when <4GB, unchanged otherwise Closes #1072 --- packages/engine/src/config.ts | 30 +++++++++++++++---- .../engine/src/services/browserManager.ts | 25 ++++++++++++++-- 2 files changed, 47 insertions(+), 8 deletions(-) diff --git a/packages/engine/src/config.ts b/packages/engine/src/config.ts index ab0871ad2..5bc4c5e93 100644 --- a/packages/engine/src/config.ts +++ b/packages/engine/src/config.ts @@ -208,6 +208,29 @@ export const DEFAULT_CONFIG: EngineConfig = { debug: false, }; +function getSystemFreeMb(): number { + try { + const { freemem } = require("os") as typeof import("os"); + return Math.floor(freemem() / (1024 * 1024)); + } catch { + return 8192; + } +} + +function memoryAdaptiveCacheLimit(): number { + const free = getSystemFreeMb(); + if (free < 2048) return 32; + if (free < 4096) return 64; + return DEFAULT_CONFIG.frameDataUriCacheLimit; +} + +function memoryAdaptiveCacheBytesMb(): number { + const free = getSystemFreeMb(); + if (free < 2048) return 128; + if (free < 4096) return 256; + return DEFAULT_CONFIG.frameDataUriCacheBytesLimitMb; +} + /** * Resolve configuration by merging: defaults ← env vars ← explicit overrides. * Env vars provide backward compatibility during migration; explicit config @@ -298,14 +321,11 @@ export function resolveConfig(overrides?: Partial): EngineConfig { audioGain: envNum("PRODUCER_AUDIO_GAIN", DEFAULT_CONFIG.audioGain), frameDataUriCacheLimit: Math.max( 32, - envNum("PRODUCER_FRAME_DATA_URI_CACHE_LIMIT", DEFAULT_CONFIG.frameDataUriCacheLimit), + envNum("PRODUCER_FRAME_DATA_URI_CACHE_LIMIT", memoryAdaptiveCacheLimit()), ), frameDataUriCacheBytesLimitMb: Math.max( 64, - envNum( - "PRODUCER_FRAME_DATA_URI_CACHE_BYTES_MB", - DEFAULT_CONFIG.frameDataUriCacheBytesLimitMb, - ), + envNum("PRODUCER_FRAME_DATA_URI_CACHE_BYTES_MB", memoryAdaptiveCacheBytesMb()), ), playerReadyTimeout: envNum( diff --git a/packages/engine/src/services/browserManager.ts b/packages/engine/src/services/browserManager.ts index 6ea640361..0f9cae2c7 100644 --- a/packages/engine/src/services/browserManager.ts +++ b/packages/engine/src/services/browserManager.ts @@ -8,7 +8,7 @@ import type { Browser, PuppeteerNode } from "puppeteer-core"; import { existsSync, readdirSync } from "fs"; import { join } from "path"; -import { homedir } from "os"; +import { freemem, homedir } from "os"; import { DEFAULT_CONFIG, type EngineConfig } from "../config.js"; let _puppeteer: PuppeteerNode | undefined; @@ -476,6 +476,24 @@ export function _setPuppeteerForTests(mock: PuppeteerNode | undefined): void { _puppeteer = mock; } +function getFreeMb(): number { + return Math.floor(freemem() / (1024 * 1024)); +} + +function getGpuMemBudgetMb(): number { + const free = getFreeMb(); + if (free < 2048) return 512; + if (free < 4096) return 1024; + return 4096; +} + +function getLowMemoryFlags(): string[] { + const free = getFreeMb(); + if (free >= 4096) return []; + const heapMb = free < 2048 ? 256 : 512; + return [`--js-flags=--max-old-space-size=${heapMb}`]; +} + export interface BuildChromeArgsOptions { width: number; height: number; @@ -530,9 +548,10 @@ export function buildChromeArgs( "--disable-print-preview", "--no-pings", "--no-zygote", - // Memory - "--force-gpu-mem-available-mb=4096", + // Memory — scale GPU budget to available system RAM + `--force-gpu-mem-available-mb=${getGpuMemBudgetMb()}`, "--disk-cache-size=268435456", + ...getLowMemoryFlags(), // Disable features that add overhead "--disable-features=AudioServiceOutOfProcess,IsolateOrigins,site-per-process,Translate,BackForwardCache,IntensiveWakeUpThrottling", ];