0

I'm working on a React Electron app, and I've been plagued with an event emitter memory leak in multiple areas of my app. I'm not extremely experienced with Node.js so forgive my amateur mistakes.

(node:3142) MaxListenersExceededWarning: Possible EventEmitter memory leak detected. 11 file:minified listeners added to [EventEmitter]. Use emitter.setMaxListeners() to increase limit

Let me note that this legitimately giving me problems in the long run of my app, so this isn't a problem I would simply "solve" or silence with setMaxListeners().

Basically, the user drops some files, and a chain begins. I create an event process that adds drops minified files into a list in the renderer process:

  1. For each file dropped, send the file over to the main process for modification.

    App.js (render process):
document.ondrop = (e) => {
        e.preventDefault();
        e.stopPropagation();
        handleFiles(e.dataTransfer.files);
    };

const sendFiles = (files) => {
        for (let file of files) {
            file = file.path || file;

            // if file is a folder, recursively check each file in the nested folders
            if (fs.lstatSync(file).isDirectory()) {
                // rename for clarity
                let folder = file;
                let files = fs.readdirSync(folder).map((fileName) => {
                    return path.join(folder, fileName);
                });
                sendFiles(files);
            } else {
                // don't add duplicate files to list
                if (list.some((item) => item.oPath === file.path)) {
                    return false;
                }
                ipcRenderer.send("file:add", file);
            }
        }
    };
  1. Minify each individual file and send the minified paths back to the renderer process to display.

    main.js (main process):
ipcMain.on("file:add", (e, path) => {
        minifyFile(path, mainWindow);
    });

const minifyFile = (filePath, mainWindow) => {
...
        mainWindow.webContents.send(
            "file:minified",
            {
                path: saveLocation,
                name: name,
                type: extension,
                oSize: originalSize,
                nSize: newSize,
                oPath: filePath,
                newName: newName,
            }
        );
...
}
  1. Now that we have the file info for each minified file to display, add it to our list state in React.

    App.js (render process again):
ipcRenderer.on("file:minified", (e, data) => {
        // if item's path already exists on list, don't add it
        if (list.some((item) => item.path === data.path)) {
            return false;
        } else {
            let newList = list;
            newList.push({
                name: data.name,
                path: data.path,
                type: data.type,
                oSize: data.oSize,
                nSize: data.nSize,
                oPath: data.oPath,
                newName: data.newName,
            });

            setList(newList);
            console.log(list);
        }
});

When this happens, the files get added as expected, but as I continue to drop more files throughout the lifetime of the program, the event emitters called for "file:minified" grow exponentially, and this starts causing issues for me in other parts of my program, as now it tries to add items to the list nearly hundreds of times.

If someone would help me out finding the error I'm making, I'd appreciate it because I'm kind of blind when it comes to these problems in Node.js.

Is this something I need to remove event listeners for? As I said, I'm not too sure about any of this.

I've had another memory leak issue in the renderer process of my app too, anyone's interested in checking it out to see if they could possibly be linked; I have absolutely no idea. Here's my other thread.

coddingtonjoel
  • 189
  • 2
  • 12
  • This can usually happen when you mount components that subscribe to events but then don't unsubscribe to them when they unmount. Can you update your question to include a [Minimal, Complete, and Reproducible](https://stackoverflow.com/help/minimal-reproducible-example) example of your component code? – Drew Reese Jun 24 '20 at 04:46
  • Please refer this answer https://stackoverflow.com/questions/9768444/possible-eventemitter-memory-leak-detected – kevin wang Dec 21 '21 at 12:48

1 Answers1

0

it looks like you are receiving a single response from the main process for each send. If this is the case you can use ipcRenderer.invoke() and ipcMain.handle() like...

App.js

document.ondrop = (e) => {
        e.preventDefault();
        e.stopPropagation();
        handleFiles(e.dataTransfer.files);
    };

const sendFiles = (files) => {
        for (let file of files) {
            file = file.path || file;

            // if file is a folder, recursively check each file in the nested folders
            if (fs.lstatSync(file).isDirectory()) {
                // rename for clarity
                let folder = file;
                let files = fs.readdirSync(folder).map((fileName) => {
                    return path.join(folder, fileName);
                });
                sendFiles(files);
            } else {
                // don't add duplicate files to list
                if (list.some((item) => item.oPath === file.path)) {
                    return false;
                }
                ipcRenderer.invoke("file:add", file)
                    .then((data) => handleMinified(data)})
            }
        }
    };

const handleMinified = (data) => {
    // if item's path already exists on list, don't add it
    if (list.some((item) => item.path === data.path)) {
        return false;
        } 
    else {
        let newList = list;
        newList.push({
            name: data.name,
            path: data.path,
            type: data.type,
            oSize: data.oSize,
            nSize: data.nSize,
            oPath: data.oPath,
            newName: data.newName,
            });

            setList(newList);
            console.log(list);
        }

    };

main.js

ipcMain.handle("file:add", (e, path) => {
        const data = await minifyFile(path);
        return data;
    });


const minifyFile = (filePath, mainWindow) => {
    ...
            return {
                path: saveLocation,
                name: name,
                type: extension,
                oSize: originalSize,
                nSize: newSize,
                oPath: filePath,
                newName: newName,
            }
}

This way, event listeners are removed for you :)

https://www.electronjs.org/docs/api/ipc-renderer#ipcrendererinvokechannel-args