'Puppeteer create PDF files from HTML data hangs Windows 10 system

I created an App that processes students results by extracting data from multiple excel workbooks. The problem is that using Puppeteer to generate the PDF files, throws the system into a loop till it hangs the system.

Actually, I have tested same codes below using PhantomJs which is bundled as pdf-creator-node, and was able to generate 150 PDF files comfortably in 3 minutes. The only challenge I dumped PhantomJs is that all the styling in the CSS file was not included, even when I inserted it as an inline style in the header, suing replace function of JS. Another, is that PhantomJs is no longer in active development. I searched the web, and found out that only Puppeteer is the valid solution with active development and support too.

I tried using page.close() at the end of pdfCreator() which is in a loop, and browser.close() at the end of pdfGenerator(). What I am doing wrong?

Here below are the codes in the server.js and PdfGenerator.js files, with a sample of the ERROR, and screenshot of my Task Manager after the system crawled out of hanging state. For HTML generation, I used Mustache. I excluded some lines of codes in server.js because the total character count was over 60k.

server.js


// [codes were removed here]


        if(getCode == 'compute-result') {
          // declare variable
          let setData = null;
          let setTitle = 'Results Computation...';
          let setArgs = getArgs;
          // dataFromFile = ReadFile(pathCodeTextFile);
          // setArgs = Number(dataFromFile);
          setCode = 'compute-result';
          let setView = [];
          let setNext = true;
          let countTerms = [];
          
          // if(getArg > 0) {

            // Final Result computation
            const getJson = ReadFile(pathJsonResults);
            // const getCtrl = ReadFile(pathJsonCtrl);
            const getResultObject = JSON.parse(getJson);
            getResult = getResultObject;
            const totalResults = getResult.firstTerm.length + getResult.secondTerm.length + getResult.thirdTerm.length;

            if(setView.length < 1 && getResult != null) {
              setData = 'PDFs for Students Results initiating...';
              setView.unshift('Reading saved data...');
              client.emit('query', {data: setData, title: setTitle, code: setCode, next: setNext, args: null, view: JSON.stringify(setView)});
            }

          Sleep(2000).then(() => {

            if(getResult != null) {          
              setData = 'Students Results will be ready in a moment';
              client.emit('query', {data: setData, title: setTitle, code: setCode, next: setNext, args: setArgs, view: JSON.stringify(setView)});
            }

            const wacthFiles = (file, className, termName, sessionName, completed, pdfList) => {
              try {
                if(typeof file == 'string' && !FileExists(pathJsonPdfList)) {

                  if(pdfList.length < 2){
                    setData = 'Saving PDFs to downladable files...';
                  }

                  if(className != null && termName != null && sessionName != null) {
                    setTitle = `${pdfList.length} Result PDF${pdfList.length > 1?'s':''}...`;
                    setView.unshift(file);
                    if(!countTerms.includes(termName)) {
                      countTerms.push(termName)
                    }

                    // setCode = -1000 - pdfList.length;
                    // console.log('PDF PROGRESS: ', `${pdfList.length} Result PDF${pdfList.length > 1?'s':''}... ${setCode}`);
                  
                    // when all PDFs are created
                    if(completed) {
                      setTitle = setTitle.replace('...', ' [completed]');
                      setData = 'Result Download button is Active. You may click it now.';
                      setView.unshift('=== PDF GENERATION COMPLETED ===');
                      setView.unshift(`A total of ${pdfList.length} students' Results were generated`);
                      WriteFile(pathJsonPdfList, JSON.stringify(pdfList));

                      // set donwload button active
                      setCode = Number(codeTextFilePdfCompleted);
                      setNext = false;
                      getResult = null;
                      let termString = countTerms.toString();
                      termString = ReplaceAll(termString, '-term', '');
                      termString = ReplaceAll(termString, ',', '-');
                      const addTxt = `${className} _${termString} Term${countTerms.length>1?'s':''} (${sessionName})`;
                      WriteFile(pathCodeTextFile, addTxt);
                      // console.log('======== PDF GENERATION ENDS ================');
                    } else {
                      setCode = -1 * pdfList.length;
                    }
                      client.emit('query', {data: setData, title: setTitle, code: setCode, next: setNext, args: setArgs, view: JSON.stringify(setView)});
                    }
                }
                
              } catch (error) {
                console.log('ERROR ON WATCHER: ', error);
              }
            }


            if(!FileExists(pathJsonPdfList) && getResult !== null) {
              PdfGenerator(getResult, wacthFiles);
            }

            // Watcher(pathWatchResults, setCode, wacthDir, 10000);
          });
          // }
        }


      }
    } catch (error) {
  })

  client.on('disconnect', () => {
    console.log('SERVER: Disconnected');
});



server.listen(portApi, () =>{
  console.log('Server listens on port 8881')
});

// serve static files
app.use(express.static(pathPublic));

// [codes were removed here]

PdfGenerator.js The problem lies in these functions: PdfGenerator & createPdf

'use strict';
process.setMaxListeners(Infinity) // fix for Puppeteer MaxListenerExceededWarning
const Puppeteer = require('puppeteer')
const {HtmlGenerator} = require('../components/HtmlGenerator')
const {WriteFile, FileExists, RandomNumber, RoundNumber, IsNumberFraction, ReadFile} = require('../components/Functions')


if (process.env.NODE_ENV !== 'production') {
    require('dotenv').config();
}

const pathFirstTermResults = process.env.DIR_FIRST_TERM_RESULTS;
const pathSecondTermResults = process.env.DIR_SECOND_TERM_RESULTS;
const pathThirdTermResults = process.env.DIR_THIRD_TERM_RESULTS;
const publicDir = process.env.DIR_PUBLIC;
const cssFile = process.env.PATH_CSS_FILENAME;
const pathCssRaw = __dirname + '\\' + publicDir + '\\' + cssFile;
const pathCss = pathCssRaw.replace(`\\uploads`, '');
const tagCssReplace = process.env.TAG_CSS_REPLACE;
let jsonDir = process.env.PATH_JSON;
jsonDir = jsonDir.split('/').pop();
let htmlDir = process.env.DIR_HTML;
htmlDir = __dirname + '\\' + htmlDir.split('/').pop();
const htmlType1 = htmlDir +  '\\' + process.env.HTML_TYPE1;
const htmlType2 = htmlDir +  '\\' + process.env.HTML_TYPE2;
const htmlType3 = htmlDir +  '\\' + process.env.HTML_TYPE3;
const pathJsonPdfList = './' + jsonDir + '/' + process.env.JSON_PDF_LIST_FILENAME;
const pathJsonPdfContent = __dirname + '\\' + jsonDir + '\\' + process.env.JSON_PDF_CONTENT;

const firstTermDir = 'first-term';
const secondTermDir = 'second-term';
const thirdTermDir = 'third-term';

let cumulativeFirstTermTotalList = {};
let cumulativeSecondTermTotalList = {};

let firstTermOnce = true;
let secondTermOnce = true;
let thirdTermOnce = true;
let isActive = false;

const getPath = (p, f) => {
    let dir = pathFirstTermResults;
    switch (p) {
        case firstTermDir:
            dir = pathFirstTermResults;
            break;
        case secondTermDir:
            dir = pathSecondTermResults;
            break;
        case thirdTermDir:
            dir = pathThirdTermResults;
            break;
    
        default:
            break;
    }
    return dir + f
}

const resolution = {
    x: 1920,
    y: 1080
}

const args = [
    '--disable-gpu',
    `--window-size=${resolution.x},${resolution.y}`,
    '--no-sandbox',
]

const createPdf = (page, content, templateType, filename, className, term, sessionName, isProcessActive, pdfFileList, cb) => {
    
    let path, document, options;
    path = getPath(term, filename);

    if(path != null) {

        let options = {
            path: path,
            format: 'A4',
            printBackground: true,
            margin: {
                left: '0px',
                top: '0px',
                right: '0px',
                bottom: '0px'
            }
        }
        
        let templateData = '';
        switch (templateType) {
            case '1':
                templateData = ReadFile(htmlType1);
                break;
            case '2':
                templateData = ReadFile(htmlType2);
                break;
            case '3':
                templateData = ReadFile(htmlType3);
                break;
        
            default:
                templateData = ReadFile(htmlType1);
                break;
        }
        
        (async() => {
            const html = HtmlGenerator(content, templateData);

            if(html != undefined && html !== '' && html != null) {
            // create PDF file
            cb(filename, className, term, sessionName, isProcessActive, pdfFileList);

                // get style from .css & replace
                const css = ReadFile(pathCss);

                await page.setContent(html, { waitUntil: 'networkidle0'});
                await page.addStyleTag(css);
                await page.pdf(options);
                page.close();
            }
        })()
    }
}


const pdfGenerator = (json, cb) => {
    let data  = {};
    let pdfFileList = [];

    if(typeof json == 'string') {
        data = JSON.parse(json)
    } else {
        data = json;
    }

    try {        

    // declare defaults
    let filename = 'Student' + '.pdf';
    let termName = firstTermDir;
    const templateType = data.keys.templateType;
    const session = data.classInfo.Session;
    const sessionName = session.replace('/', '-');
    const students = data.students;
    const className = data.classInfo.Class_Name;
    const recordFirstTerm = data.firstTerm;
    const recordSecondTerm = data.secondTerm;
    const recordThirdTerm = data.thirdTerm;
    
    let pdfCreatedList = [];
    let isReset = false;

    let totalResultsExpected = Object.keys(recordFirstTerm).length + Object.keys(recordSecondTerm).length + Object.keys(recordThirdTerm).length;
    let totalResultsCount = 0;
    let jsonForPdf = {};
    let record = {};
    let sRecord, path, id, fName, lName;

    // get each student 
    let logEndOnce = true;
    let logBeforeOnce = true;
    logBeforeOnce && console.log('==============    ***     ================');
    logBeforeOnce && console.log('======== PDF GENERATION BEGINS ================');
    

    const computeResult = (page, setTerm, setRecord, setReset) => {
        const termName = setTerm;
        const record = setRecord;
        let isReset = setReset;

        logBeforeOnce && console.log(`====== ${termName} RESULTS BEGINS ======`);
            for(let elem of students){
                id = elem.id;
                fName = elem.firstName;
                lName = elem.lastName;
                filename = `${lName} ${fName} _${termName} ${sessionName}.pdf`;
                // sRecord = record.filter(function (entry) { return entry[id] !== undefined; });
                sRecord = record[id];
                path = getPath(termName, filename);
    
                // create pdf
                if(!FileExists(path) && !FileExists(pathJsonPdfList)){
                
                    // generate final JSON for the student
                    // isReset = (pdfCreatedList.includes(id))? false: true;
                    
                    jsonForPdf = finalJson(elem, sRecord, data, termName);
                    (pdfFileList.length < 1) && WriteFile(pathJsonPdfContent, JSON.stringify(jsonForPdf));
        
                    pdfFileList.push({
                      'term': termName,
                      'file': filename
                    });
                    totalResultsCount = pdfFileList.length;
                    const pdfDate = new Date();
                    console.log(`${filename} (${totalResultsCount}/${totalResultsExpected}) at ${pdfDate.getHours()}hr${pdfDate.getHours()>1?'s':''} - ${pdfDate.getMinutes()}min${pdfDate.getMinutes()>1?'s':''} - ${pdfDate.getSeconds()}sec${pdfDate.getSeconds()>1?'s':''}`);

                    isActive = (totalResultsExpected === totalResultsCount)? true: false;
                    logEndOnce = false;
                    // cb(filename, className, termName, sessionName, isActive, pdfFileList);
                    // WriteFile(path, null);
                    isReset = true;
                    createPdf(page, jsonForPdf, templateType, filename, className, termName, sessionName, isActive, pdfFileList, cb);
                }
            }


            logBeforeOnce && console.log(`====== ${termName} RESULTS ENDS ======`);
    }

    // get each student result for First Term
    const computeFirstTerm = (p) => {
        return new Promise((resolve) => {
            if(data.keys.firstTerm === '1') {
                termName = firstTermDir;
                record = recordFirstTerm;
                pdfCreatedList = [];
                isReset = false;

                computeResult(p, termName, record, isReset)
            }
            resolve()
        })
    }

    // get each student result for Second Term
    const computeSecondTerm = (p) => {
        return new Promise((resolve) => {
            if(data.keys.secondTerm === '1') {
                termName = secondTermDir;
                record = recordSecondTerm;
                pdfCreatedList = [];
                isReset = false;

                computeResult(p, termName, record, isReset)
            }
            resolve()
        })
    }

    // get each student result for Third Term
    const computeThirdTerm = (p) => {
        return new Promise((resolve) => {
            if(data.keys.thirdTerm === '1') {
                termName = thirdTermDir;
                record = recordThirdTerm;
                pdfCreatedList = [];
                isReset = false;

                computeResult(p, termName, record, isReset)
            }
            resolve()
        })
    }

    (async () => {
        browser = await Puppeteer.launch({
            headless: true,
            handleSIGINT: false,
            args: args,
        });

        const page = await browser.newPage();
    
        await page.setViewport({
            width: resolution.x,
            height: resolution.y,
        })

        await computeFirstTerm(page);
        await computeSecondTerm(page);
        await computeThirdTerm(page);
        browser.close()
    })()
    

    
    if(totalResultsExpected === totalResultsCount && totalResultsCount !== 0 && !logEndOnce) {
        logEndOnce = true;
        logBeforeOnce = false;
        console.log('======== PDF GENERATION ENDS ================');
    }



    } catch (error) {
        console.log('==== ERROR IN PDF GENERATION: ', error)
    }


}

module.exports = {
    PdfGenerator: pdfGenerator
}

ERROR

info Visit https://yarnpkg.com/en/docs/cli/run for documentation about this command.

lerna ERR! yarn run start stderr:

<--- Last few GCs --->

[9884:000002D68A73C6B0]  1665171 ms: Scavenge 44.1 (45.8) -> 43.2 (45.8) MB, 223.9 / 0.0 ms  (average mu = 0.956, current mu = 0.952) allocation failure
[9884:000002D68A73C6B0]  1684089 ms: Scavenge 44.1 (45.8) -> 43.3 (45.8) MB, 587.3 / 0.0 ms  (average mu = 0.956, current mu = 0.952) allocation failure
[9884:000002D68A73C6B0]  1749901 ms: Scavenge 44.2 (45.8) -> 43.3 (45.8) MB, 5099.0 / 0.0 ms  (average mu = 0.956, current mu = 0.952) allocation failure


<--- JS stacktrace --->

FATAL ERROR: Committing semi space failed. Allocation failed - JavaScript heap out of memory
 1: 00007FF6ED61013F
 2: 00007FF6ED59F396
 3: 00007FF6ED5A024D
 4: 00007FF6EDED19EE
 5: 00007FF6EDEBBECD
 6: 00007FF6EDD5F61C
 7: 00007FF6EDD6933F
 8: 00007FF6EDD5BF19
 9: 00007FF6EDD5A0D0
10: 00007FF6EDD7EA06
11: 00007FF6EDAB1CD5
12: 00007FF6EDF5F3E1
13: 00007FF6EDF602E9
14: 000002D68C4EF69E
error Command failed with exit code 134.

Screenshot of Task Manager, Chromium running multiple instances of over 50.

enter image description here

I appreciate any help. I hope this can be resolved to give me a smooth PDF generation. Thank you.



Sources

This article follows the attribution requirements of Stack Overflow and is licensed under CC BY-SA 3.0.

Source: Stack Overflow

Solution Source