fix: hot-swap browser restart to prevent QUEUE_FULL with single browser
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 9m21s
All checks were successful
Build & Deploy to Staging / Build & Deploy to Staging (push) Successful in 9m21s
Root cause: With BROWSER_COUNT=1, the hourly browser restart set restarting=true, drained all pages, closed the browser, THEN launched a new one. During that window (seconds), all requests queued and timed out after 30s with QUEUE_FULL errors. Fix: Launch the new browser BEFORE closing the old one (hot-swap). This ensures zero downtime during browser recycling, even with a single browser instance.
This commit is contained in:
parent
253d03f58a
commit
609e7d0808
1 changed files with 29 additions and 16 deletions
|
|
@ -99,9 +99,21 @@ export function releasePage(page: Page, inst: BrowserInstance): void {
|
||||||
async function scheduleRestart(inst: BrowserInstance): Promise<void> {
|
async function scheduleRestart(inst: BrowserInstance): Promise<void> {
|
||||||
if (inst.restarting) return;
|
if (inst.restarting) return;
|
||||||
inst.restarting = true;
|
inst.restarting = true;
|
||||||
logger.info(`Scheduling browser ${inst.id} restart`);
|
logger.info(`Scheduling browser ${inst.id} restart (hot-swap)`);
|
||||||
|
|
||||||
// Wait for pages to drain (max 30s)
|
// Launch new browser FIRST so we never have zero capacity
|
||||||
|
const newBrowser = await puppeteer.launch({
|
||||||
|
headless: true,
|
||||||
|
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || undefined,
|
||||||
|
args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu", "--disable-dev-shm-usage",
|
||||||
|
"--disable-background-networking", "--disable-default-apps", "--disable-extensions",
|
||||||
|
"--disable-sync", "--disable-translate", "--metrics-recording-only",
|
||||||
|
"--no-first-run", "--safebrowsing-disable-auto-update"],
|
||||||
|
});
|
||||||
|
const newPages = await createPages(newBrowser, PAGES_PER_BROWSER);
|
||||||
|
|
||||||
|
// Wait for in-flight pages to drain (max 30s)
|
||||||
|
const oldBrowser = inst.browser;
|
||||||
await Promise.race([
|
await Promise.race([
|
||||||
new Promise<void>(resolve => {
|
new Promise<void>(resolve => {
|
||||||
const check = () => {
|
const check = () => {
|
||||||
|
|
@ -113,23 +125,24 @@ async function scheduleRestart(inst: BrowserInstance): Promise<void> {
|
||||||
new Promise<void>(r => setTimeout(r, 30000)),
|
new Promise<void>(r => setTimeout(r, 30000)),
|
||||||
]);
|
]);
|
||||||
|
|
||||||
for (const page of inst.availablePages) await page.close().catch(() => {});
|
// Swap: install new browser and pages, then clean up old
|
||||||
inst.availablePages.length = 0;
|
const oldPages = inst.availablePages.splice(0);
|
||||||
try { await inst.browser.close(); } catch {}
|
inst.browser = newBrowser;
|
||||||
|
inst.availablePages.push(...newPages);
|
||||||
inst.browser = await puppeteer.launch({
|
|
||||||
headless: true,
|
|
||||||
executablePath: process.env.PUPPETEER_EXECUTABLE_PATH || undefined,
|
|
||||||
args: ["--no-sandbox", "--disable-setuid-sandbox", "--disable-gpu", "--disable-dev-shm-usage",
|
|
||||||
"--disable-background-networking", "--disable-default-apps", "--disable-extensions",
|
|
||||||
"--disable-sync", "--disable-translate", "--metrics-recording-only",
|
|
||||||
"--no-first-run", "--safebrowsing-disable-auto-update"],
|
|
||||||
});
|
|
||||||
inst.availablePages.push(...await createPages(inst.browser, PAGES_PER_BROWSER));
|
|
||||||
inst.jobCount = 0;
|
inst.jobCount = 0;
|
||||||
inst.lastRestartTime = Date.now();
|
inst.lastRestartTime = Date.now();
|
||||||
inst.restarting = false;
|
inst.restarting = false;
|
||||||
logger.info(`Browser ${inst.id} restarted`);
|
logger.info(`Browser ${inst.id} restarted (hot-swap complete)`);
|
||||||
|
|
||||||
|
// Clean up old browser in background
|
||||||
|
for (const page of oldPages) await page.close().catch(() => {});
|
||||||
|
try { await oldBrowser.close(); } catch {}
|
||||||
|
|
||||||
|
// Drain any waiters now that pages are available
|
||||||
|
while (waitingQueue.length > 0 && inst.availablePages.length > 0) {
|
||||||
|
const waiter = waitingQueue.shift()!;
|
||||||
|
waiter.resolve({ page: inst.availablePages.pop()!, instance: inst });
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
export async function initBrowser(): Promise<void> {
|
export async function initBrowser(): Promise<void> {
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue