Skip to content

Commit 743aeba

Browse files
committed
fix(catalog-browser): clean stale Chromium locks before launch
Previous failed launches left SingletonLock files + orphan chromium processes in userDataDir, blocking new launches with 'profile appears to be in use by another Chromium process' error. Added: - cleanStaleLocks() method called before every browser launch: - Removes SingletonLock, SingletonCookie, SingletonSocket files - Kills orphan chromium processes via pkill - browser.on('disconnected') handler to clean up internal state - protocolTimeout: 60000ms for slow container startup Signed-off-by: Kelvin Yuli Andrian <kelvinzer0@users.noreply.github.com>
1 parent b0cd83b commit 743aeba

1 file changed

Lines changed: 57 additions & 0 deletions

File tree

src/api/integrations/channel/whatsapp/catalog-browser.service.ts

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -24,6 +24,10 @@
2424
* (Kelvin Yuli Andrian's own implementation, which proves this works)
2525
*/
2626

27+
import { existsSync, unlinkSync } from 'fs';
28+
import { join } from 'path';
29+
import { execSync } from 'child_process';
30+
2731
import { Logger } from '@config/logger.config';
2832
import { BadRequestException } from '@exceptions';
2933
import puppeteer, { Browser, Page } from 'puppeteer-core';
@@ -520,17 +524,36 @@ export class BrowserCatalogService {
520524
const userDataDir = this.sessionStore.userDataDir(instanceName);
521525
this.logger.log(`[browser] Launching Chromium for instance=${instanceName} jid=${jid}`);
522526

527+
// Clean up stale SingletonLock file from previous crashed launches.
528+
// Chromium creates this lock file to prevent concurrent profile access,
529+
// but if a previous process crashed, the lock stays and blocks new launches.
530+
this.cleanStaleLocks(userDataDir);
531+
523532
const browser = await puppeteer.launch({
524533
executablePath: this.config.executablePath,
525534
headless: this.config.headless,
526535
userDataDir,
527536
args: this.config.extraArgs,
528537
defaultViewport: { width: 1280, height: 800 },
529538
ignoreDefaultArgs: ['--enable-automation'],
539+
// Wait for initial page to be ready before returning
540+
protocolTimeout: 60000,
530541
});
531542

532543
this.browsers.set(jid, browser);
533544

545+
// Handle unexpected browser disconnect — clean up so next call can re-launch
546+
browser.on('disconnected', () => {
547+
this.logger.warn(`[browser] Browser disconnected for jid=${jid}, cleaning up`);
548+
this.browsers.delete(jid);
549+
const timer = this.idleTimers.get(jid);
550+
if (timer) {
551+
clearTimeout(timer);
552+
this.idleTimers.delete(jid);
553+
}
554+
this.pendingQr.delete(jid);
555+
});
556+
534557
// Navigate to WA Web on the first page
535558
const page = await browser.newPage();
536559
await page.goto('https://web.whatsapp.com/', {
@@ -602,6 +625,40 @@ export class BrowserCatalogService {
602625
this.idleTimers.set(jid, timer);
603626
}
604627

628+
/**
629+
* Remove stale Chromium lock files and kill orphan Chromium processes
630+
* left over from previous crashed launches.
631+
*
632+
* Chromium creates SingletonLock, SingletonCookie, and SingletonSocket
633+
* symlinks in the userDataDir. If a previous process crashed, these
634+
* locks persist and block new launches with "profile appears to be in
635+
* use by another Chromium process" error.
636+
*/
637+
private cleanStaleLocks(userDataDir: string): void {
638+
// 1. Remove lock files/symlinks
639+
const lockFiles = ['SingletonLock', 'SingletonCookie', 'SingletonSocket'];
640+
for (const lockFile of lockFiles) {
641+
const lockPath = join(userDataDir, lockFile);
642+
if (existsSync(lockPath)) {
643+
try {
644+
unlinkSync(lockPath);
645+
this.logger.log(`[browser] Removed stale lock: ${lockFile}`);
646+
} catch (err) {
647+
this.logger.warn(`[browser] Failed to remove ${lockFile}: ${(err as Error).message}`);
648+
}
649+
}
650+
}
651+
652+
// 2. Kill orphan chromium processes (best-effort, ignore errors)
653+
// This handles the case where a previous Puppeteer crash left
654+
// chromium processes running and holding the profile.
655+
try {
656+
execSync('pkill -f chromium 2>/dev/null || true', { timeout: 5000 });
657+
} catch {
658+
// pkill exit code 1 = no process matched, ignore
659+
}
660+
}
661+
605662
/**
606663
* Kill the browser for a JID and clean up timers.
607664
*/

0 commit comments

Comments
 (0)