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!
This commit is contained in:
Emi Matchu 2021-08-19 23:38:25 -07:00
parent f036890aa1
commit fc52439387

View file

@ -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();
}
}