Skip to content

Commit 6bb0580

Browse files
feat: add automatic Chrome temporary file cleanup system
Implements comprehensive cleanup mechanism to prevent disk space exhaustion from accumulated Chrome/Chromium temporary files in Docker containers. ## Changes ### Core Implementation (app/core/browser-pool.js) - Add cleanupChromeTempFiles() method to remove old temp files - Implement periodic cleanup (every 5 minutes, configurable) - Add cleanup on browser destruction lifecycle hook - Add cleanup on graceful shutdown - Track cleanup metrics (tempFilesCleanedUp counter) ### Docker Configuration (Dockerfile) - Add findutils package for file cleanup operations - Create cleanup script at /usr/local/bin/cleanup-chrome-temp.sh ## Problem Solved Chrome creates temporary files (.com.google.Chrome.*) that accumulate in /tmp during Puppeteer operations. These files can consume significant disk space over time, potentially causing: - Disk space exhaustion - Application crashes - Performance degradation ## Solution Three-layer cleanup approach: 1. Automatic periodic cleanup (every 5 minutes) 2. Cleanup on browser destruction (after each browser closes) 3. Final cleanup on graceful shutdown
1 parent b04a59a commit 6bb0580

2 files changed

Lines changed: 101 additions & 3 deletions

File tree

Dockerfile

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,7 +7,7 @@ WORKDIR /usr/src/app
77

88
ENV PUPPETEER_SKIP_CHROMIUM_DOWNLOAD true
99

10-
RUN apt-get update && apt-get install curl gnupg ffmpeg -y \
10+
RUN apt-get update && apt-get install curl gnupg ffmpeg findutils -y \
1111
&& curl --location --silent https://dl-ssl.google.com/linux/linux_signing_key.pub | apt-key add - \
1212
&& sh -c 'echo "deb [arch=amd64] http://dl.google.com/linux/chrome/deb/ stable main" >> /etc/apt/sources.list.d/google.list' \
1313
&& apt-get update \
@@ -23,6 +23,14 @@ RUN npm install --os=linux --cpu=x64 sharp
2323
# Copy the rest of your app's source code
2424
COPY . .
2525

26+
# Create cleanup script for Chrome temp files
27+
RUN echo '#!/bin/sh\n\
28+
# Clean Chrome temporary files\n\
29+
find /tmp -name ".com.google.Chrome.*" -type f -mmin +5 -delete 2>/dev/null || true\n\
30+
find /tmp -name "puppeteer_dev_chrome_profile-*" -type d -mmin +30 -exec rm -rf {} + 2>/dev/null || true\n\
31+
find /tmp -name "Crashpad" -type d -mmin +30 -exec rm -rf {} + 2>/dev/null || true\n\
32+
' > /usr/local/bin/cleanup-chrome-temp.sh && chmod +x /usr/local/bin/cleanup-chrome-temp.sh
33+
2634
# Expose the port your app runs on
2735
EXPOSE 3000
2836

app/core/browser-pool.js

Lines changed: 92 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,17 +1,23 @@
11
import puppeteer from "puppeteer";
22
import genericPool from "generic-pool";
33
import { EventEmitter } from "events";
4+
import { exec, execSync } from "child_process";
5+
import { promisify } from "util";
6+
7+
const execAsync = promisify(exec);
48

59
class BrowserPool extends EventEmitter {
610
constructor() {
711
super();
812
this.pool = null;
913
this.isShuttingDown = false;
14+
this.cleanupInterval = null;
1015
this.metrics = {
1116
totalAcquired: 0,
1217
totalReleased: 0,
1318
totalErrors: 0,
1419
totalRecycled: 0,
20+
tempFilesCleanedUp: 0,
1521
};
1622
}
1723

@@ -21,14 +27,21 @@ class BrowserPool extends EventEmitter {
2127
"--disable-setuid-sandbox",
2228
"--disable-dev-shm-usage",
2329
"--disable-gpu",
24-
"--no-first-run",
30+
"--single-process",
2531
"--no-zygote",
32+
"--disable-web-security",
33+
"--disable-features=VizDisplayCompositor",
2634
"--disable-background-timer-throttling",
2735
"--disable-backgrounding-occluded-windows",
2836
"--disable-renderer-backgrounding",
37+
"--disk-cache-dir=/tmp/chrome-cache",
38+
"--disk-cache-size=104857600", // 100MB max
39+
"--media-cache-size=104857600",
40+
"--aggressive-cache-discard",
41+
"--temp-dir=/tmp", // Explicitly set temp directory
42+
"--no-first-run",
2943
"--disable-features=site-per-process",
3044
"--disable-blink-features=AutomationControlled",
31-
"--disable-web-security",
3245
"--disable-features=IsolateOrigins,site-per-process",
3346
"--disable-hang-monitor",
3447
"--disable-default-apps",
@@ -133,6 +146,11 @@ class BrowserPool extends EventEmitter {
133146
}
134147
}
135148

149+
// Clean up Chrome temp files after browser destruction
150+
this.cleanupChromeTempFiles().catch((err) => {
151+
console.warn("Non-critical: Failed to clean Chrome temp files:", err.message);
152+
});
153+
136154
this.emit("browserDestroyed", {
137155
duration: Date.now() - startTime,
138156
lifetime: Date.now() - resource.createdAt
@@ -231,12 +249,76 @@ class BrowserPool extends EventEmitter {
231249
// Setup graceful shutdown
232250
this.setupShutdownHandlers();
233251

252+
// Start periodic cleanup of Chrome temp files
253+
this.startPeriodicCleanup(options.cleanupInterval || 5 * 60 * 1000); // Default: 5 minutes
254+
234255
// Pre-warm if not disabled
235256
if (options.warmUp !== false) {
236257
this.warmUp().catch(console.error);
237258
}
238259
}
239260

261+
/**
262+
* Clean up Chrome temporary files
263+
* Removes .com.google.Chrome.* files that are older than 5 minutes
264+
*/
265+
async cleanupChromeTempFiles() {
266+
try {
267+
const commands = [
268+
// Clean /tmp directory
269+
'find /tmp -name ".com.google.Chrome.*" -type f -mmin +5 -delete 2>/dev/null || true',
270+
// Clean user data directories
271+
'find /tmp -name "puppeteer_dev_chrome_profile-*" -type d -mmin +30 -exec rm -rf {} + 2>/dev/null || true',
272+
// Clean Chrome crash dumps
273+
'find /tmp -name "Crashpad" -type d -mmin +30 -exec rm -rf {} + 2>/dev/null || true',
274+
];
275+
276+
for (const cmd of commands) {
277+
try {
278+
await execAsync(cmd);
279+
} catch (err) {
280+
// Ignore errors - files might not exist or already deleted
281+
}
282+
}
283+
284+
this.metrics.tempFilesCleanedUp++;
285+
this.emit("tempFilesCleanedUp");
286+
} catch (error) {
287+
console.warn("Chrome temp file cleanup warning:", error.message);
288+
}
289+
}
290+
291+
/**
292+
* Start periodic cleanup interval
293+
*/
294+
startPeriodicCleanup(intervalMs) {
295+
// Clear existing interval if any
296+
if (this.cleanupInterval) {
297+
clearInterval(this.cleanupInterval);
298+
}
299+
300+
// Run cleanup immediately
301+
this.cleanupChromeTempFiles().catch(() => {});
302+
303+
// Setup periodic cleanup
304+
this.cleanupInterval = setInterval(() => {
305+
this.cleanupChromeTempFiles().catch(() => {});
306+
}, intervalMs);
307+
308+
console.log(`Chrome temp file cleanup scheduled every ${intervalMs / 1000} seconds`);
309+
}
310+
311+
/**
312+
* Stop periodic cleanup
313+
*/
314+
stopPeriodicCleanup() {
315+
if (this.cleanupInterval) {
316+
clearInterval(this.cleanupInterval);
317+
this.cleanupInterval = null;
318+
console.log("Periodic cleanup stopped");
319+
}
320+
}
321+
240322
async warmUp() {
241323
try {
242324
const minSize = this.pool.min;
@@ -400,8 +482,16 @@ class BrowserPool extends EventEmitter {
400482
this.isShuttingDown = true;
401483

402484
try {
485+
// Stop periodic cleanup
486+
this.stopPeriodicCleanup();
487+
488+
// Drain and clear the pool
403489
await this.pool.drain();
404490
await this.pool.clear();
491+
492+
// Final cleanup of temp files
493+
await this.cleanupChromeTempFiles();
494+
405495
console.log("Browser pool drained and cleared");
406496
this.emit("poolDrained");
407497
} catch (error) {

0 commit comments

Comments
 (0)