Recycle wedged idle browsers sooner

This commit is contained in:
Pradeep Elankumaran
2026-05-31 01:25:49 -07:00
parent 5ab9175817
commit 80a28281ec
5 changed files with 56 additions and 5 deletions
+1 -1
View File
@@ -38,6 +38,6 @@ primary_region = "sjc"
[env]
NODE_ENV = "production"
SESSION_TIMEOUT_MS = "600000"
BROWSER_IDLE_TIMEOUT_MS = "3900000"
BROWSER_IDLE_TIMEOUT_MS = "900000"
MAX_SESSIONS = "10"
MAX_OLD_SPACE_SIZE = "512"
+2 -2
View File
@@ -4,7 +4,7 @@
import crypto from 'crypto';
import { monitorEventLoopDelay } from 'perf_hooks';
import { collectResourceSnapshot, classifyProxyError, browserProcessTreeRssMb } from './resources.js';
import { collectResourceSnapshot, classifyProxyError, browserProcessTreeRssMb, browserProcessNameRssMb } from './resources.js';
// ============================================================================
// Anonymization
@@ -434,7 +434,7 @@ export function createTabHealthTracker(page) {
// collectResourceSnapshot and classifyProxyError live in lib/resources.js
// (isolated from network code for clean separation of concerns).
// Re-exported here for backward compatibility.
export { collectResourceSnapshot, classifyProxyError, browserProcessTreeRssMb };
export { collectResourceSnapshot, classifyProxyError, browserProcessTreeRssMb, browserProcessNameRssMb };
// ============================================================================
// Rate limiter (sliding window, 1 hour)
+42
View File
@@ -55,6 +55,48 @@ export function browserProcessTreeRssMb(browserPid) {
return Math.round(totalKb / 1024);
}
const BROWSER_PROCESS_NAMES = new Set([
'camoufox-bin',
'firefox',
'Web Content',
'WebExtensions',
'Socket Process',
'Utility Process',
'RDD Process',
'Privileged Cont',
]);
function readProcessName(pid) {
const comm = fs.readFileSync(`/proc/${pid}/comm`, 'utf8').trim();
if (comm) return comm;
return '';
}
/**
* Fallback browser RSS scanner for Camoufox/Firefox. Playwright's browser.process()
* can be unavailable depending on the launcher path, but the process names are
* still visible in /proc inside the Fly machine.
*/
export function browserProcessNameRssMb() {
if (process.platform !== 'linux') return null;
let totalKb = 0;
let found = false;
for (const pid of fs.readdirSync('/proc')) {
if (!/^\d+$/.test(pid)) continue;
try {
const name = readProcessName(pid);
if (!BROWSER_PROCESS_NAMES.has(name)) continue;
totalKb += readVmRssKb(pid);
found = true;
} catch {
// Process exited or /proc access failed; skip it.
}
}
return found ? Math.round(totalKb / 1024) : null;
}
export function collectResourceSnapshot(opts = {}) {
const mem = process.memoryUsage();
const snap = {
+2 -2
View File
@@ -33,7 +33,7 @@ import {
import { actionFromReq, classifyError } from './lib/request-utils.js';
import { cleanupOrphanedTempFiles, cleanupStaleFirefoxProfiles } from './lib/tmp-cleanup.js';
import { coalesceInflight } from './lib/inflight.js';
import { createReporter, createTabHealthTracker, collectResourceSnapshot, classifyProxyError, browserProcessTreeRssMb } from './lib/reporter.js';
import { createReporter, createTabHealthTracker, collectResourceSnapshot, classifyProxyError, browserProcessTreeRssMb, browserProcessNameRssMb } from './lib/reporter.js';
import { mountDocs } from './lib/openapi.js';
import { initSentry, captureException as sentryCaptureException, setupExpressErrorHandler as setupSentryErrorHandler, flush as sentryFlush } from './lib/sentry.js';
import { prepareExternalCamoufoxExecutable } from './lib/camoufox-executable.js';
@@ -5150,7 +5150,7 @@ setInterval(() => {
if (sessions.size > 0 || !browser) return;
const mem = process.memoryUsage();
const nativeMemMb = Math.round((mem.rss - mem.heapUsed) / 1048576);
const browserRssMb = browserProcessTreeRssMb(_browserPid());
const browserRssMb = browserProcessTreeRssMb(_browserPid()) ?? browserProcessNameRssMb();
if (browserRssMb !== null && browserRssMb >= CONFIG.browserRssRestartThresholdMb) {
log('warn', 'browser rss pressure, restarting browser', {
+9
View File
@@ -0,0 +1,9 @@
import { describe, expect, test } from '@jest/globals';
import { browserProcessNameRssMb } from '../../lib/resources.js';
describe('browserProcessNameRssMb', () => {
test('returns null on non-linux platforms or no browser processes', () => {
const value = browserProcessNameRssMb();
expect(value === null || Number.isInteger(value)).toBe(true);
});
});