Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Node Process does not exit until device is disconnected #173

Open
bradfol opened this issue Dec 5, 2016 · 12 comments
Open

Node Process does not exit until device is disconnected #173

bradfol opened this issue Dec 5, 2016 · 12 comments

Comments

@bradfol
Copy link

bradfol commented Dec 5, 2016

When running a Node app from the command line, I have noticed that the Node process will not exit until the HID is disconnected from the computer. I have observed this behavior on both a Mac (macOS 10.12.1, Node v4.6.0) and Raspberry Pi 3 (Node 4.5.0).

A couple examples:

If I use the ctrl-C shortcut on a running Node app, I will not be returned to the prompt until I disconnect the USB HID that the app was communicating with.

Similarly, if the app crashes because of an un-caught error, it will not return to the prompt until I disconnect the USB HID.

I had not experienced this behavior before beginning to use the node-hid library. Any clarification on why the app is behaving this way is appreciated.

@todbot
Copy link
Contributor

todbot commented Dec 5, 2016

Could you give a link to your code?

@bradfol
Copy link
Author

bradfol commented Dec 6, 2016

@todbot After a bit more digging, this seems to be an interaction with the bleno library.

Here is the smallest test case I could make to reproduce the issue:

"use strict";
const HID = require("node-hid");
require("bleno");

function findDevice() {
  const devices = HID.devices();
  for (const device of devices) {
    if (device.vendorId === 2049 && device.productId === 2) { // I'm looking for a card reader
      console.log("HID found");
      return new HID.HID(device.path);
    }
  }
}

const hid = findDevice();

hid.on("data", function (buffer) {
  console.log(buffer);
});

@amadsen
Copy link

amadsen commented Dec 6, 2016

I have experienced this too, in a different project. In my case it was more obvious when there was a different bug with writing to the device (going off of memory at the moment.) My suspicion is the read loop opened to support the .on("data",...) event handler is keeping the process open, but that's not something I have tested yet.

@todbot
Copy link
Contributor

todbot commented Dec 6, 2016

Yes, it's the EventEmitter inside node-hid keeping the process. Isn't this expected behavior?

To cancel the pending read, you should call hid.close().

@bradfol
Copy link
Author

bradfol commented Dec 6, 2016

@todbot Yes, the listener on the EventEmitter will keep the process running until it is removed. I understand that is expected behavior.

What I am describing is that the Node process will not exit when sent a SIGINT by pressing ctrl-C nor will it exit if there is an uncaught error. That is not expect behavior.

Please let me know if you were able to run the sample app and see what I am observing. Thanks.

@todbot
Copy link
Contributor

todbot commented Dec 7, 2016

Hmm. Yes, I can kill the Node process by doing Ctrl-C in the terminal. I've not tried killing it though on the commandline though. Maybe Node doesn't respond to SIGINT, but just the standard SIGTERM?

@todbot
Copy link
Contributor

todbot commented Dec 7, 2016

I just verified a simple hid.on('data',...) example does respond to SIGINT and SIGTERM on my Macbook Pro running OS X 10.11.6, Node v6.9.1, node-hid from github (e.g. npm install github:node-hid/node-hid) and running this code to capture trackpad movements:
https://github.com/node-hid/node-hid/blob/master/src/test-macbookprotrackpad.js

I don't have a test for the currently published node-hid on npmjs, so that could conceivably be different, since there is a new version of the underlying hidapi C library in the github version.

The command I use to test and kill is:

node ./node_modules/node-hid/src/test-macbookprotrackpad.js & ; sleep 5; killall -v node -INT

@bradfol
Copy link
Author

bradfol commented Dec 7, 2016

@todbot Can you run the same simple example but also install bleno and add require("bleno"); to load that module as well. Then see if the app still responds to SIGINT? Those are the conditions that recreate the bug for me.

@xopr
Copy link

xopr commented May 14, 2019

After a long search I came to notice I have the same issue.
This snippet listens to keyboard, barcode scanner and a timeout; it will not exit the process unless you uncomment the first new Promise(.. which listens to HID data:

"use strict";
const readline = require( "readline" );
const rl = readline.createInterface( { input: process.stdin, output: process.stdout } );
const HID = require('node-hid');
// Connect to a USB barcode scanner with `xinput disable <id>` so it doesn't send text to stdin
const hid = new HID.HID( 5050, 24 );

var timer;
async function testhid()
{
    var input = await Promise.race(
    [ 
        new Promise( resolve => { hid.once( "data", resolve ) } ),
        new Promise( resolve => { rl.once( "line", resolve ) } ),
        new Promise( resolve => { timer = setTimeout( resolve, 10000, 'TIMEOUT' ) } )
    ] );
    console.log( input );
}

(async () => {
    await testhid();
    clearTimeout( timer );
    rl.close();
    hid.close();
})();

Did anyone find a way to (force) disconnect the listener and/or HID?
:edit:
This might actually be a duplicate of #61

@peitschie
Copy link

I had this issue also, and found that I could workaround it by calling setNonBlocking(true) on the HID device prior to subscribing to any data.

There's an open issue from 2017 about this undocumented function: #216

@todbot
Copy link
Contributor

todbot commented Jun 19, 2019

node-hid is a rather thin wrapper on https://github.com/libusb/hidapi, including setNonBlocking().

Whatever setNonBlocking(true) is doing that's odd to you is in hidapi, not node-hid. (But having said that, we are finally moving forward with getting some needed patches added to hidapi, so some outstanding node-hid issues may get resolved "magically")

To do async reading, hidapi (and thus node-hid) sets up threads to watch for input, so the device.on('data',...) events work. Without a device.close() there's no way to know if the program should exit or still wait for more data.

As we get more updates to hidapi, I hope to delve more into this issue. If anyone wants to look deeper into this, in the hidapi API, you can see all hid_set_non_blocking() is supposed to do is change whether or not hid_read() immediately returns 0 if no data is available or wait until data is available.

It's very possible there are bugs in the code that handles this and it will be very platform-dependent, since Mac, Windows, and Linux all do HID in different ways.

If anyone can provide a small working bit of sample code that goes against a commonly available piece of hardware, please let me know. We will get the hardware and test it with hidapi and then node-hid.

@alvaro-cuesta
Copy link

alvaro-cuesta commented May 5, 2024

This is still happening to me when calling process.exit(0) in Electron which, as far as I can tell, should also close any open emitters? Works in Node though.

Unfortunately setNonBlocking(true) also generates a non-descriptive error so I cannot confirm whether that'd fix my issue. Using .setNonBlocking(1) did not fix the issue for me.

Is this related to electron/electron#20425? The description matches. Why wouldn't this impact regular Node?

This code reproduces the issue in Electron but not in Node:

HIDAsync.open(0x3434, 0x0363)
  .then(hid => {
    console.log('Listening for data...')

    hid.on('data', data => {
      console.log(data)
    })

    setTimeout(() => {
      console.log('Exiting')
      process.exit(0)
    }, 5000)
  })

Removing the hid.on('data', ... ) section correctly exits in Electron.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

No branches or pull requests

6 participants