I have a Lambda function which reads zip codes from an SQS queue and attempts to scrape network availability info from a webpage containing a map. For that the Lambda function intercepts image responses and counts the number of pixels of each color (corresponding to the network availability type).
The problem I'm facing is that if I test it on my local computer or I run a single test on Lambda it works ok. Also it works fine when I just start sending messages to SQS, but when there are some number of messages in the SQS queue already and Lambda is running in parallel, almost 100% of executions fail with Target closed error or Session closed error. It seems like this error happens when puppeteer attempts to do a certain action with the page, but the page is closed already. Most of the time this error happens after/during the page load or before/while waiting for the first selector. There are some suggestions that the page can crash due to memory/cpu limits, and I tried to increase puppeteer memory to 2Gb and Lambda memory limit to 2.5Gb, which should be enough I suppose for a single page, but it still keeps crashing. Interestingly enough that if I run it only once it succeeds.
Also I got a few times 'EventEmitter memory leak detected. 11 exit listeners added' erorr and tried to add the line process.setMaxListeners(0);, but it didn't help.
Lambda code:
const chromium = require('chrome-aws-lambda');
const PNG = require('pngjs').PNG;
const Promise = require('bluebird');
function groupByFour(arr) {
const result = [];
const len = Math.floor(arr.length / 4);
for (let i = 0; i < len; i++) {
result.push(arr.slice(4 * i, 4 * (i + 1)));
}
return result;
}
function addImageToGroupStats(hex, allUniques, num, group) {
const arr = hex.match(/../g);
// Pixel format R G B A
const groupedArr = groupByFour(arr);
const uniques = {};
groupedArr.forEach(group => {
const key = group.join('');
uniques[key] = (uniques[key] || 0) + 1;
allUniques[key] = (allUniques[key] || 0) + 1;
})
console.log(num, group, uniques);
}
exports.handler = main = async (event, context) => {
process.setMaxListeners(0);
const { messageId, body } = event.Records[0];
console.log('SQS message %s: %j', messageId, body);
const postcode = body.padStart(5, '0');
let lastImageFoundTs = null; // to wait for image loading
let waitForImagesStarted = null; // to reject after 15 sec
let startTrackingPics = false; // to avoid tracking initial images
let imageCount = 0; // total images count
const allUniques4G = {}; // number of pixels of each color for all images
// Images urls for 4G/LTE Map
const lteImageUrls = [];
function waitForImages() {
return new Promise( (resolve, reject) => {
const timer = setInterval(function() {
if (lastImageFoundTs && Date.now() - lastImageFoundTs > 2500) {
clearInterval(timer);
return resolve();
}
if (!waitForImagesStarted) {
waitForImagesStarted = Date.now();
}
if (Date.now() - waitForImagesStarted > 15000) {
clearInterval(timer);
return reject(`${postcode}: waited for images more than 15 sec`);
}
}, 500);
});
}
const browser = await chromium.puppeteer.launch({
executablePath: await chromium.executablePath,
args: [...chromium.args, "--shm-size=2gb"],
defaultViewport: chromium.defaultViewport,
headless: chromium.headless,
devtools: true,
slowMo: 250
});
const page = await browser.newPage();
await page.setRequestInterception(true);
page.on('request', function(request) {
request.continue();
});
page.on('response', async function(response) {
try {
if (/4GDataCoverage\/MapServer\/tile/i.test(response.url()) && startTrackingPics) {
if (response.ok) {
imageCount++;
let num = imageCount;
lteImageUrls.push(response.url());
lastImageFoundTs = Date.now();
const buff = await response.buffer();
lastImageFoundTs = Date.now();
const png = PNG.sync.read(buff);
// Pixels array
const hex = png.data.toString('hex');
addImageToGroupStats(hex, allUniques4G, num, '4GDataCoverage');
}
}
} catch(e) {
console.log(postcode, '4GDataCoverage Callback error:', e.message);
}
});
await page.setViewport({ width: 1366, height: 768 });
await page.goto('https://www.verizonwireless.com/reusable-content/landing-page/coverage-map.html', {waitUntil: 'load', timeout: 0});
await page.waitFor('#searchbox input');
await page.type('#searchbox input', postcode);
startTrackingPics = true;
await page.click('#btn-submit-usearch');
await waitForImages();
console.log(`Pixels of all 4GDataCoverage images`, allUniques4G);
try {
await browser.close();
} catch(e) {
console.log('Error:', e.message);
}
return allUniques4G;
}
Error example:
{
"errorType": "Error",
"errorMessage": "Protocol error (DOM.resolveNode): Target closed.",
"message": "Protocol error (DOM.resolveNode): Target closed.",
"stack": [
"Error: Protocol error (DOM.resolveNode): Target closed.",
" at /var/task/node_modules/puppeteer-core/lib/Connection.js:183:56",
" at new Promise (<anonymous>)",
" at CDPSession.send (/var/task/node_modules/puppeteer-core/lib/Connection.js:182:12)",
" at ExecutionContext._adoptBackendNodeId (/var/task/node_modules/puppeteer-core/lib/ExecutionContext.js:190:41)",
" at ExecutionContext._adoptElementHandle (/var/task/node_modules/puppeteer-core/lib/ExecutionContext.js:207:17)",
" at async Frame.waitForSelector (/var/task/node_modules/puppeteer-core/lib/FrameManager.js:631:20)",
" at async Runtime.main [as handler] (/var/task/index.js:170:5)",
" -- ASYNC --",
" at Frame.<anonymous> (/var/task/node_modules/puppeteer-core/lib/helper.js:111:15)",
" at Frame.waitFor (/var/task/node_modules/puppeteer-core/lib/FrameManager.js:612:19)",
" at Page.waitFor (/var/task/node_modules/puppeteer-core/lib/Page.js:1113:29)",
" at Runtime.main [as handler] (/var/task/index.js:170:16)",
" at runMicrotasks (<anonymous>)",
" at runNextTicks (internal/process/task_queues.js:62:5)",
" at listOnTimeout (internal/timers.js:518:9)",
" at processTimers (internal/timers.js:492:7)"
]
}
Don't know how to debug it further. Any suggestions are greatly appreciated.