2

How can I avoid memory leaks caused by event listeners and event emissions in my Electron app, specifically when continuously reading data from a connected device through a serial port?

I have developed a desktop app using Electron.js and TypeScript. The purpose of this app is to read real-time data from a device connected to it. The app needs to continuously read both analog and digital data from the device. Initially, when the device is connected and data reading is started by listening to the serial port session, everything works fine. However, after approximately 2 hours of continuous data reading from the serial port session, an error occurs:

[electron] <--- JS stacktrace ---> [electron] [electron] FATAL ERROR: Cannot grow ExternalPointerTable past its maximum capacity Allocation failed - process out of memory

To provide you with more information, here is the code I have written:

First, the open() method is responsible for opening the serial port connected to the device:

open(): Promise<void> {
    return new Promise<void>(async (resolve, reject) => {
      const port = new SerialPort(
        {
          path: this.sessionInfo.path,
          baudRate: this.sessionInfo.buadRate,
          dataBits: 8,
          stopBits: 2,
          parity: "none",
        },

        function (err) {
          if (err) {
            reject(err);
          }
        }
      );
      this.dataReaderParser = this.sessionInfo.readLineParser;

      port.pipe(this.bufferCleanerParser);
      this.bufferCleanerParser.on("data", (data: Buffer) => {
        console.log(`Cleared data: ${data.toString("hex")}`);
      });
      await new Promise<void>((resolve) => {
        setTimeout(() => {
          port.unpipe(this.bufferCleanerParser);
          port.pipe(this.dataReaderParser);
          resolve();
        }, 10);
      });

      const onData = (data: string) => {
        if (this.onDataCallback) {
          this.onDataCallback(data);
        }
        this.emit('data', data);
      };

      this.dataReaderParser.on('data', onData);

      this.port = port;
      resolve();
    });
  }

Next, the read() method is used to listen for emitted data:

public read(
    callback: (data: string) => void,
    sendFlag?: boolean
  ): Promise<string> {
    if (callback) {
      return new Promise<string>((resolve, reject) => {
        if (sendFlag) {
          this.onDataCallback = (data: string) => {
            callback(data);
          };

          return;

        }

        this.on("data", (data) => {
          callback(data);
        });
        this.removeListener('data', callback)
      });
    }
    return Promise.resolve("the callback is not provided");
  }

Finally, the readAnalog() method is called when the start button is pressed on the UI to continuously read data from the connected device:

async readAnalog(decoder: DecoderInterface, callback: (data: PowerDeliveryAnalogModel|any) => void) {
    try {
      this.writeDigital()
      const session = this.sessionManager.getSession("serialPortSession2");

      session.read(async (data: string) => {
        try {
          this.counter++;
          if (this.counter === 50) {
            const parserAnalogData = this.parser.parseAnalogData(data);
            const res: PowerDeliveryAnalogModel =
              decoder.decodeAnalogData(parserAnalogData);
            callback(res);
            this.counter = 0;
          }
        } catch (error) {}
      });
    } catch (error) {}
  }

so how we can avoid memory leak that occur by event listener and emit the event as i guess in my case?

  • 1
    Unlikely to be related to the memory leak, but [never pass an `async function` as the executor to `new Promise`](https://stackoverflow.com/q/43036229/1048572)! – Bergi Jun 18 '23 at 22:11

0 Answers0