From fc52439387b2602031e1629b5b02fd52b6e0e754 Mon Sep 17 00:00:00 2001 From: Matchu Date: Thu, 19 Aug 2021 23:38:25 -0700 Subject: [PATCH] Reboot assetImage browser when it disappears Been seeing this in testing in prod, the first few images worked great, but then eventually they all started saying the browser was disconnected. Here, we add a check to reconnect if it goes missing. This is actually kinda hard to test in dev, because the dev server creates a new process every time the function runs, so fingers crossed! I also added explicit logic to close the page when we're done with it, I'm worried we crashed the browser by exceeding the RAM limit by leaving pages open. Not sure quite how their model works and whether things eventually get flushed out on their own! --- api/assetImage.js | 52 +++++++++++++++++++++++++++++------------------ 1 file changed, 32 insertions(+), 20 deletions(-) diff --git a/api/assetImage.js b/api/assetImage.js index cf9d82d..b71ad9f 100644 --- a/api/assetImage.js +++ b/api/assetImage.js @@ -37,6 +37,13 @@ const ASSET_IMAGE_PAGE_BASE_URL = process.env.VERCEL_URL // parallel will be a problem for our API endpoint. let BROWSER; async function getBrowser() { + // Sometimes, the browser crashes. Maybe a RAM thing? If that happened, set + // it to null, and then we'll replace it with a new instance below. + if (BROWSER && !BROWSER.isConnected()) { + console.info("Browser is no longer connected; rebooting."); + BROWSER = null; + } + if (!BROWSER) { if (process.env["NODE_ENV"] === "production") { // In production, we use a special chrome-aws-lambda Chromium. @@ -102,28 +109,33 @@ async function loadAndScreenshotImage(libraryUrl, size) { const page = await browser.newPage(); console.debug("Page opened, navigating to: " + assetImagePageUrl.toString()); - await page.goto(assetImagePageUrl.toString()); - console.debug("Page loaded, awaiting image"); + try { + await page.goto(assetImagePageUrl.toString()); + console.debug("Page loaded, awaiting image"); - // Start looking for the loaded canvas, *and* for an error message. - // When either one displays, we proceed, either by returning the image if - // present, or raising the error if present. - const imageBufferPromise = screenshotImageFromPage(page); - const errorMessagePromise = readErrorMessageFromPage(page); - const firstResultFromPage = await Promise.any([ - imageBufferPromise.then((imageBuffer) => ({ imageBuffer })), - errorMessagePromise.then((errorMessage) => ({ errorMessage })), - ]); + // Start looking for the loaded canvas, *and* for an error message. + // When either one displays, we proceed, either by returning the image if + // present, or raising the error if present. + const imageBufferPromise = screenshotImageFromPage(page); + const errorMessagePromise = readErrorMessageFromPage(page); + const firstResultFromPage = await Promise.any([ + imageBufferPromise.then((imageBuffer) => ({ imageBuffer })), + errorMessagePromise.then((errorMessage) => ({ errorMessage })), + ]); - if (firstResultFromPage.errorMessage) { - throw new Error(firstResultFromPage.errorMessage); - } else if (firstResultFromPage.imageBuffer) { - return firstResultFromPage.imageBuffer; - } else { - throw new Error( - `Assertion error: Promise.any did not return an errorMessage or an imageBuffer: ` + - `${JSON.stringify(Object.keys(firstResultFromPage))}` - ); + if (firstResultFromPage.errorMessage) { + throw new Error(firstResultFromPage.errorMessage); + } else if (firstResultFromPage.imageBuffer) { + return firstResultFromPage.imageBuffer; + } else { + throw new Error( + `Assertion error: Promise.any did not return an errorMessage or an imageBuffer: ` + + `${JSON.stringify(Object.keys(firstResultFromPage))}` + ); + } + } finally { + // Close the page, to save on RAM in our shared browser instance. + page.close(); } }