Puppeteer Target closed Error on AWS Lambda

Viewed 654

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.

0 Answers
Related