diegomura / react-pdf

📄 Create PDF files using React
https://react-pdf.org
MIT License
15.02k stars 1.19k forks source link

MaxListenersExceededWarning: Possible EventEmitter memory leak detected #2825

Open Torbraw opened 4 months ago

Torbraw commented 4 months ago

Describe the bug When trying to render multiple times the same pdf with renderToBuffer() in a loop in a Nest.js app (with express), I get the following warnings in the console. Note that all the render() methods yield the same error:

(node:41468) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 unhandledRejection listeners added to [process]. Use emitter.setMaxListeners() to increase limit
(Use 'node --trace-warnings ...' to show where the warning was created)
(node:41468) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 uncaughtException listeners added to [process]. Use emitter.setMaxListeners() to increase limit

If I add process.on('warning', e => console.warn(e.stack)); to have more infos here's the results:

MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 unhandledRejection listeners added to [process]. Use emitter.setMaxListeners() to increase limit
    at genericNodeError (node:internal/errors:984:15)
    at wrappedFn (node:internal/errors:538:14)
    at _addListener (node:events:593:17)
    at process.addListener (node:events:611:10)
    at loadAssembly (C:\Workspace\flexyb-pay\node_modules\yoga-layout\binaries\wasm-async-node.js:11:63)
    at Object.loadYoga (C:\Workspace\flexyb-pay\node_modules\yoga-layout\src\entrypoint\wasm-async-node.ts:25:29)
    at _callee$ (C:\Workspace\flexyb-pay\node_modules\@react-pdf\layout\lib\index.cjs:698:34)
    at tryCatch (C:\Workspace\flexyb-pay\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:45:16)
    at Generator.<anonymous> (C:\Workspace\flexyb-pay\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:133:17)
    at Generator.next (C:\Workspace\flexyb-pay\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:74:21)
    at asyncGeneratorStep (C:\Workspace\flexyb-pay\node_modules\@babel\runtime\helpers\asyncToGenerator.js:3:17)
    at _next (C:\Workspace\flexyb-pay\node_modules\@babel\runtime\helpers\asyncToGenerator.js:17:9)
    at C:\Workspace\flexyb-pay\node_modules\@babel\runtime\helpers\asyncToGenerator.js:22:7
    at new Promise (<anonymous>)
    at C:\Workspace\flexyb-pay\node_modules\@babel\runtime\helpers\asyncToGenerator.js:14:12
    at loadYoga (C:\Workspace\flexyb-pay\node_modules\@react-pdf\layout\lib\index.cjs:718:17)
MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 uncaughtException listeners added to [process]. Use emitter.setMaxListeners() to increase limit
    at genericNodeError (node:internal/errors:984:15)
    at wrappedFn (node:internal/errors:538:14)
    at _addListener (node:events:593:17)
    at process.addListener (node:events:611:10)
    at loadAssembly (C:\Workspace\flexyb-pay\node_modules\yoga-layout\binaries\wasm-async-node.js:11:9)
    at Object.loadYoga (C:\Workspace\flexyb-pay\node_modules\yoga-layout\src\entrypoint\wasm-async-node.ts:25:29)
    at _callee$ (C:\Workspace\flexyb-pay\node_modules\@react-pdf\layout\lib\index.cjs:698:34)
    at tryCatch (C:\Workspace\flexyb-pay\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:45:16)
    at Generator.<anonymous> (C:\Workspace\flexyb-pay\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:133:17)
    at Generator.next (C:\Workspace\flexyb-pay\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:74:21)
    at asyncGeneratorStep (C:\Workspace\flexyb-pay\node_modules\@babel\runtime\helpers\asyncToGenerator.js:3:17)
    at _next (C:\Workspace\flexyb-pay\node_modules\@babel\runtime\helpers\asyncToGenerator.js:17:9)
    at C:\Workspace\flexyb-pay\node_modules\@babel\runtime\helpers\asyncToGenerator.js:22:7
    at new Promise (<anonymous>)
    at C:\Workspace\flexyb-pay\node_modules\@babel\runtime\helpers\asyncToGenerator.js:14:12
    at loadYoga (C:\Workspace\flexyb-pay\node_modules\@react-pdf\layout\lib\index.cjs:718:17)

To Reproduce Steps to reproduce the behavior including code snippet (if applies):

  1. Clone the following repository
  2. Run npm i & npm run start:dev
  3. Go to localhost:3000/api to see the swagger-ui
  4. Call the get endpoint and see the warnings in the console of your IDE

Expected behavior The eventEmitters should be disposed/close correctly and there should be no warnings.

Desktop (please complete the following information):

PatricioNG commented 4 months ago

Can confirm I've run into the same and went looking to see if I was the only one, seems like a potential memory leak 👀 On ^3.4.4 running on Node/Windows server 2019 on this end.

leiteszeke commented 4 months ago

Exactly the same issue. I have been receiving this in the last 3 months at least.

I am using "@react-pdf/renderer": "^3.4.4"

(node:98733) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 uncaughtException listeners added to [process]. Use emitter.setMaxListeners() to increase limit
    at _addListener (node:events:591:17)
    at process.addListener (node:events:609:10)
    at /var/www/daruma/graphql/node_modules/yoga-layout/binaries/wasm-async-node.js:11:9
    at Object.loadYoga (/var/www/daruma/graphql/node_modules/yoga-layout/src/entrypoint/wasm-async-node.js:35:43)
    at _callee$ (/var/www/daruma/graphql/node_modules/@react-pdf/layout/lib/index.cjs:707:34)
    at tryCatch (/var/www/daruma/graphql/node_modules/@babel/runtime/helpers/regeneratorRuntime.js:44:17)
    at Generator.<anonymous> (/var/www/daruma/graphql/node_modules/@babel/runtime/helpers/regeneratorRuntime.js:125:22)
    at Generator.next (/var/www/daruma/graphql/node_modules/@babel/runtime/helpers/regeneratorRuntime.js:69:21)
    at asyncGeneratorStep (/var/www/daruma/graphql/node_modules/@babel/runtime/helpers/asyncToGenerator.js:3:24)
    at _next (/var/www/daruma/graphql/node_modules/@babel/runtime/helpers/asyncToGenerator.js:22:9)
    at /var/www/daruma/graphql/node_modules/@babel/runtime/helpers/asyncToGenerator.js:27:7
    at new Promise (<anonymous>)
    at /var/www/daruma/graphql/node_modules/@babel/runtime/helpers/asyncToGenerator.js:19:12
    at loadYoga (/var/www/daruma/graphql/node_modules/@react-pdf/layout/lib/index.cjs:727:17)
    at _callee$ (/var/www/daruma/graphql/node_modules/@react-pdf/layout/lib/index.cjs:738:18)
karlandindrakryggen commented 3 months ago

I've noticed the same issue. I'm not sure, but suspect that it can have been introduced in the switch from @react-pdf/yoga to yoga-layout. At least I get the dependency chains below when making a fresh installation with the different versions, and it's only with @react-pdf/renderer@2.1.1 (the latest version with @react-pdf/yoga) that work without the memory issue in my tests.

One clue might be that the yoga-layout readme says that you must call config.free()/node.free()?

2.1.1

└─┬ @react-pdf/renderer@2.1.1
  └─┬ @react-pdf/layout@2.1.1
    └── @react-pdf/yoga@2.0.4

2.1.2

└─┬ @react-pdf/renderer@2.1.2
  └─┬ @react-pdf/layout@3.12.1
    └── yoga-layout@2.0.1

latest (3.4.4)

└─┬ @react-pdf/renderer@3.4.4
  └─┬ @react-pdf/layout@3.12.1
    └── yoga-layout@2.0.1

Update: I've created a simple example that generates 1000 pdf documents and prints memory usage: https://github.com/karlandindrakryggen/react-pdf-memory-issue. I've included printouts for different versions of @react-pdf/renderer in the README.

michalprikryl commented 3 months ago

We have the same issue at the nodejs (express) environment.

(node:21532) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 uncaughtException listeners added to [process]. Use emitter.setMaxListeners() to increase limit at _addListener (node:events:588:17) at process.addListener (node:events:606:10) at C:\node_modules\yoga-layout\binaries\wasm-async-node.js:11:9 at Module.loadYoga (C:\node_modules\yoga-layout\src\entrypoint\wasm-async-node.js:35:43) at _callee$ (file:///C:/node_modules/@react-pdf/layout/lib/index.js:672:23) at tryCatch (C:\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:45:16) at Generator.<anonymous> (C:\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:133:17) at Generator.next (C:\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:74:21) at asyncGeneratorStep (C:\node_modules\@babel\runtime\helpers\asyncToGenerator.js:3:17)

vipuljbhikadiya commented 3 months ago

I've noticed the same issue. I'm not sure, but suspect that it can have been introduced in the switch from @react-pdf/yoga to yoga-layout. At least I get the dependency chains below when making a fresh installation with the different versions, and it's only with @react-pdf/renderer@2.1.1 (the latest version with @react-pdf/yoga) that work without the memory issue in my tests.

One clue might be that the yoga-layout readme says that you must call config.free()/node.free()?

2.1.1

└─┬ @react-pdf/renderer@2.1.1
  └─┬ @react-pdf/layout@2.1.1
    └── @react-pdf/yoga@2.0.4

2.1.2

└─┬ @react-pdf/renderer@2.1.2
  └─┬ @react-pdf/layout@3.12.1
    └── yoga-layout@2.0.1

latest (3.4.4)

└─┬ @react-pdf/renderer@3.4.4
  └─┬ @react-pdf/layout@3.12.1
    └── yoga-layout@2.0.1

Update: I've created a simple example that generates 1000 pdf documents and prints memory usage: https://github.com/karlandindrakryggen/react-pdf-memory-issue. I've included printouts for different versions of @react-pdf/renderer in the README.

HI are you able to trace anything we are facing same issue when we are generating PDF more then 2000 pages.

karlandindrakryggen commented 3 months ago

I've noticed the same issue. I'm not sure, but suspect that it can have been introduced in the switch from @react-pdf/yoga to yoga-layout. At least I get the dependency chains below when making a fresh installation with the different versions, and it's only with @react-pdf/renderer@2.1.1 (the latest version with @react-pdf/yoga) that work without the memory issue in my tests. One clue might be that the yoga-layout readme says that you must call config.free()/node.free()? 2.1.1

└─┬ @react-pdf/renderer@2.1.1
  └─┬ @react-pdf/layout@2.1.1
    └── @react-pdf/yoga@2.0.4

2.1.2

└─┬ @react-pdf/renderer@2.1.2
  └─┬ @react-pdf/layout@3.12.1
    └── yoga-layout@2.0.1

latest (3.4.4)

└─┬ @react-pdf/renderer@3.4.4
  └─┬ @react-pdf/layout@3.12.1
    └── yoga-layout@2.0.1

Update: I've created a simple example that generates 1000 pdf documents and prints memory usage: https://github.com/karlandindrakryggen/react-pdf-memory-issue. I've included printouts for different versions of @react-pdf/renderer in the README.

HI are you able to trace anything we are facing same issue when we are generating PDF more then 2000 pages.

Hi! No, unfortunately I have not looked into this anymore.

AminArjiEpitech commented 2 months ago

Same issue here, i'll just dump my node trace and comment so i can get an update when there's one

(node:20944) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 uncaughtException listeners added to [process]. Use emitter.setMaxListeners() to increase limit
    at _addListener (node:events:587:17)
    at process.addListener (node:events:605:10)
    at loadAssembly (C:\Users\Amin\Code\Deepsy-pdf\node_modules\yoga-layout\binaries\wasm-async-node.js:11:9)
    at Object.loadYoga (C:\Users\Amin\Code\Deepsy-pdf\node_modules\yoga-layout\src\entrypoint\wasm-async-node.ts:25:29)
    at _callee$ (C:\Users\Amin\Code\Deepsy-pdf\node_modules\@react-pdf\layout\lib\index.cjs:707:34)
    at tryCatch (C:\Users\Amin\Code\Deepsy-pdf\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:45:16)
    at Generator.<anonymous> (C:\Users\Amin\Code\Deepsy-pdf\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:133:17)
    at Generator.next (C:\Users\Amin\Code\Deepsy-pdf\node_modules\@babel\runtime\helpers\regeneratorRuntime.js:74:21)
    at asyncGeneratorStep (C:\Users\Amin\Code\Deepsy-pdf\node_modules\@babel\runtime\helpers\asyncToGenerator.js:3:17)
    at _next (C:\Users\Amin\Code\Deepsy-pdf\node_modules\@babel\runtime\helpers\asyncToGenerator.js:17:9)
MarcoRodasW commented 2 months ago

Hi, is there a solution for this issue? Any workaround?

AminArjiEpitech commented 2 months ago

Edit : I just tested, this still causes memory leaks, please ignore.

Not a solution but a temporary workaround until this is resolved, you might not like it.

  1. Create a folder where you wish and save every rendered pdf's in there.
  2. Periodically clean the folder's content with conditions, in my case after every request i check the size of the folder and delete the oldest file, or simply delete every file older than an hour.

Here's the code i've used in a nodejs server :

const pdfFolder = path.join(__dirname, 'generatedPdfs');

if (!fs.existsSync(pdfFolder)) {
    fs.mkdirSync(pdfFolder);
}

// Clean folder on process startup
const cleanUpFolder = () => {
    fs.readdir(pdfFolder, (err, files) => {
        if (err) return console.error("Error reading directory:", err);
        files.forEach(file => fs.unlinkSync(path.join(pdfFolder, file)));
    });
};

cleanUpFolder();

...

// Call this to clean conditionnaly
const deleteOldFiles = () => {
    fs.readdir(pdfFolder, (err, files) => {
        if (err) return console.error("Error reading directory:", err);

        const fileStats = files.map(file => {
            const filePath = path.join(pdfFolder, file);
            return { file, time: fs.statSync(filePath).mtime };
        });

        const oneHourAgo = new Date(Date.now() - 3600000);
        fileStats.forEach(({ file, time }) => {
            if (time < oneHourAgo) {
                fs.unlinkSync(path.join(pdfFolder, file));
            }
        });

        if (fileStats.length > 20) {
            fileStats.sort((a, b) => a.time - b.time); // TODO: Try
            fileStats.slice(0, fileStats.length - 20).forEach(({ file }) => {
                fs.unlinkSync(path.join(pdfFolder, file));
            });
        }
    });
};

Also i suggest checking the filename before generating the pdf, in case you've already generated one you can directly send it.

AminArjiEpitech commented 2 months ago

@jhgeluk Nevermind, looks like this is not a workaround at all !

I just did some tests in my previous code, and i still get the exact same warning, only instead of 11 emitters, it's now 15. (node:133390) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 16 uncaughtException listeners added to [process]. MaxListeners is 15. Use emitter.setMaxListeners() to increase limit

I used both ReactPDF.render() and renderToFile() to test this, and i still get the warning.

Looks like the only solution to really workaround this is to do what i've done, using babel and transforming your normal react code into a script. This would eliminate all possibility of a memory leak, but the problem with this solution is that it is more cpu and ram expensive and may not be adapted to all, it works for me because i will most likely never have more than 1-2 renders per minute.

nnaku commented 2 months ago

It seems that loading yoga-layout wasm binaries for each pdf is causing event emitter memory leak warning.

AminArjiEpitech commented 2 months ago

@AminArjiEpitech do you have some more details about how you fixed it? Would really appreciate it! We're fine if it's a bit more expensive on the ram/cpu. As long as it doesnt keep accumulating ram, which will result in a crash.

@jhgeluk Sorry for the late reply, so basically i run my main server, and i have a folder with the script inside of it. Each time i receive a request to generate a pdf i basically just run a script (on linux a .sh), and put the necessary data in the argv as a json string that i then JSON.parse inside the script file and use as normal data.

And to not transform my React.jsx code into .js, i use this script here :

npx babel pdfFile.jsx --out-file pdfFile.js

You'll just need to isntall : npm install --save-dev @babel/node