serialport / node-serialport

Access serial ports with JavaScript. Linux, OSX and Windows. Welcome your robotic JavaScript overlords. Better yet, program them!
https://serialport.io
MIT License
5.84k stars 1.01k forks source link

Electron was frozen when closing port in main process #2439

Closed shenzhuxi closed 2 years ago

shenzhuxi commented 2 years ago

SerialPort Version

10.3.0

Node Version

16.13.2

Electron Version

17.0.1

Platform

Ubuntu 20.04 / Raspberry Pi OS11 Bullseye

Architecture

x64 / armv7l

Hardware or chipset of serialport

NAVILOCK WAVE NL-602U USB GPS

What steps will reproduce the bug?

The main.js

const { SerialPort } = require('serialport')
const { ReadlineParser } = require('@serialport/parser-readline')
const GPS = require('gps');

let gpsSetting = store.get('gps');
let gpsOptions = {
  path: gpsSetting.path,
  baudRate: gpsSetting.baudRate,
  autoOpen: false
}
let port = new SerialPort(gpsOptions);
let gps = new GPS;
// IPC listener
ipcMain.on('electron-gps-get', async (event) => {
  if (port.isOpen) {
    event.returnValue = gps.state;
  }
  else {
    event.returnValue = false;
  }
});
ipcMain.on('electron-gps-open', async (event) => {
  if (port.isOpen) {
    console.log('GPS was already on.');
  }
  else {
    gpsSetting = store.get('gps');
    gpsOptions = {
      path: '/dev/ttyACM0',
      baudRate: gpsSetting.baudRate,
      autoOpen: true
    }
    port = new SerialPort(gpsOptions);
    //https://en.wikipedia.org/wiki/NMEA_0183
    let parser = port.pipe(new ReadlineParser({ delimiter: '\r\n', encoding: 'ascii' }));
    parser.on('data', function (data) {
      gps.update(data);
    });
  }
});
ipcMain.on('electron-gps-close', async (event) => {
  port.close(function(err){
    console.log(err);
  });
});

What happens?

The port can be open in either main.js or renderer.js in Electron and the gps data can be got.

  port.close(function(err){
    console.log(err);
  });

will print null and freeze the Electron app either from the main.js or called from renderer.js with ipcRenderer.sendSync('electron-gps-close').

What should have happened?

So to work around it, I move the open, get and close code to preload.js and everything is working except

  port.close(function(err){
    console.log(err);
  });

still printed null.

I think it should be similar to port.open and have meaningful message (Although it's weird to have "is opening" as an error message).

I also found that port.open is much faster in preload.js than in main.js.

Additional information

I think https://serialport.io/docs/next/guide-electron and https://github.com/serialport/electron-serialport should be updated because contextIsolation was recommended for recent Electronjs releases https://www.electronjs.org/docs/latest/tutorial/context-isolation.

reconbot commented 2 years ago

Please do update our example and docs with the latest recommendations. I'd have to run some tests to see what I can reproduce from your example.

shenzhuxi commented 2 years ago

Hi @reconbot , I just added a little bit more information. I tried on Raspberry pi and got the same symptoms.

I also tried two other native modules https://github.com/fivdi/i2c-bus and https://github.com/kelly/node-i2c. Both of them can only be run once or twice in the main.js then freeze the Electron app. After I patched them with https://nodejs.github.io/node-addon-examples/special-topics/context-awareness/ like https://github.com/serialport/node-serialport/pull/2352/files, both of them can be run in the preload.js without problem.

shenzhuxi commented 2 years ago

I'm sorry that It's my mistake to use ipcRenderer.sendSync to call the async way to close the port.

ipcMain.on('electron-gps-close', async (event) => {
  port.close(function(err){
    console.log(err);
  });
});

https://www.electronjs.org/docs/latest/api/ipc-main#sending-messages