node-serialport
node-serialport copied to clipboard
Serial port commnunication does not work inside a Worker Thread
Summary of Problem
Opening a serial port from within a worker thread fails with:
Error: Module did not self-register.
Code to Reproduce the Issue
github repo: hugo-a-garcia/serial-worker
const {
Worker, isMainThread, parentPort, workerData
} = require('worker_threads');
const SerialPort = require("serialport");
const Readline = SerialPort.parsers.Readline;
if (isMainThread) {
console.log("In main thread");
const worker = new Worker(__filename);
worker.on('message', msg => {
console.log(msg);
});
worker.on('error', err => {
console.error("Oops " + err)
});
worker.on('exit', (code) => {
if (code !== 0)
console.log(`Worker stopped with exit code ${code}`);
});
} else {
console.log("In worker thread");
const port = new SerialPort("/dev/ttyACM0", {
baudRate: 9600
});
const parser = new Readline
port.pipe(parser);
parser.on("data", (data) => console.log(data));
// port.on('readable', function () {
// console.log(port.read().toString());
// });
port.on('error', function (err) {
console.log('Error: ', err.message);
});
parentPort.postMessage("Done.");
}
Versions, Operating System and Hardware
- SerialPort@? 7.1.5
- Node.js v? v12.9.1
- Linux
- Hardware and chipset? Arduino Uno connected to laptop
Possible related issue: N-API support for node-serialport #1186
I don't know if this would be realted to N-API. I do think you might have found a bug in workers however. =)
Ok. I submitted a bug report at node:
https://github.com/nodejs/node/issues/29698
Not a bug in Node.js. :) Quoting the Worker threads documentation:
Native add-ons can only be loaded from multiple threads if they fulfill certain conditions.
In short, your addon needs to be declared as context-aware and support multiple threads natively. For N-API, it’s assumed that that’s the case.
Just taking a quick look at the source code here, at least the usage of uv_default_loop()
is problematic and will mean that the code won’t work on worker threads as-is anyway.
Thanks @addaleax, my knowledge around figuring out how to fix this is not great. There’s no reason why we need the default event loop. And there’s no shared context between serial ports so they could in theory work in workers. I imagine the only thing we’d need to do in the cleanup hook is close the port if it’s open. (Open state is normally kept in JS)
Any pointers would be much appreciated. Thanks! 🙏
@reconbot You can probably use Node’s GetCurrentEventLoop()
and AddEnvironmentCleanupHook()
/RemoveEnvironmentCleanupHook()
functions to achieve what you need – I don’t think there are NAN wrappers for them, though.
Got to start moving to NAPI anyway 🤷♂️
Really tried to understand what has to be done to implement the cleanupHooks, but actually I'm lost in all this c++. This is not my domain :) I now forward all calls from workers to serial port running in the main thread using the message channel. This is pretty ugly but I'm fine with it at the moment O:-)
Hello. Sad news for me. Everything works if I don't use worker_threads (test.js)
const SerialPort = require('serialport')
const port = new SerialPort('/dev/tty.usbserial-A7TRF2V', { baudRate: 9600 })
port.on('data', (data) => { console.log(data) })
But if I run the same code through worker_threads I get a very sad message (main.js)
const { Worker } = require('worker_threads')
const worker = new Worker('./test.js', {})
worker.on('message', () => { })
bash-3.2$ node ./main.js
Segmentation fault: 11
In some ways, I found out the essence of the error
FATAL ERROR: v8::HandleScope::CreateHandle() Cannot create a handle without a HandleScope
1: 0x100c22043 node::Abort() (.cold.1) [/usr/local/bin/node]
2: 0x10008710d node::FatalError(char const*, char const*) [/usr/local/bin/node]
3: 0x100087276 node::OnFatalError(char const*, char const*) [/usr/local/bin/node]
4: 0x10018e2fc v8::Utils::ReportApiFailure(char const*, char const*) [/usr/local/bin/node]
5: 0x10028bd36 v8::internal::HandleScope::Extend(v8::internal::Isolate*) [/usr/local/bin/node]
6: 0x100405e0c v8::internal::JSReceiver::GetCreationContext() [/usr/local/bin/node]
7: 0x1001a0fef v8::Object::CreationContext() [/usr/local/bin/node]
8: 0x100003e0b node::MakeCallback(v8::Isolate*, v8::Local<v8::Object>, v8::Local<v8::Function>, int, v8::Local<v8::Value>*, node::async_context) [/usr/local/bin/node]
9: 0x10428d125 init(v8::Local<v8::Object>) [/Users/borisbobylev/vrack/devices/rs485/node_modules/@serialport/bindings/build/Release/bindings.node]
10: 0x10428a90b EIO_AfterOpen(uv_work_s*) [/Users/borisbobylev/vrack/devices/rs485/node_modules/@serialport/bindings/build/Release/bindings.node]
11: 0x10070f124 uv__work_done [/usr/local/bin/node]
12: 0x100712753 uv__async_io [/usr/local/bin/node]
13: 0x100722080 uv__io_poll [/usr/local/bin/node]
14: 0x100712b8a uv_run [/usr/local/bin/node]
15: 0x1000bdb79 node::NodeMainInstance::Run() [/usr/local/bin/node]
16: 0x100061cf0 node::Start(int, char**) [/usr/local/bin/node]
17: 0x7fff67a2dcc9 start [/usr/lib/system/libdyld.dylib]
Abort trap: 6
It would be great if there was someone who could fix it. It's bad that there is code that can kill the main process.
OS: MacOS Node: v14.6.0 Serialport: 9.0.0
Thanks you.
@ponikrf and anyone hitting this, you can use tiny-worker
, it appears to work fine with this and other native addons as a workaround until the worker_threads
support is implemented
Based on the discussion, it sounds like migration from NAN to N-API is a stepping stone to resolving this. Is that still understood to be the case?
I believe there was a previous endevour to migrate to N-API https://github.com/serialport/node-serialport/issues/1186
While that attempt was a while ago, I wonder what code or lessons can be gleaned from that previous work?
Hi @hugo-a-garcia would you be able to take a look at PR https://github.com/serialport/node-serialport/pull/2305 and give it a try?
While that PR isn't directly trying to address this issue, I think it might be easier to fix this issue in the N-API version. As such if you are able to share info of any errors you find, or perhaps a sample project you are trying to get to work then it might help fix this issue in the next major version of the package :-)
I am encountering this in a different situation. The callstack seems different as well than what was posted here previously. On the other hand I am not trying to use any worker threads in code that uses node serialport
.
My setup is something like this:
- rust napi native module that uses serial ports
- testing with ava the module
- spawning socat for emulating serial port
socat PTY,link=/dev/ttyS10,echo=1 PTY,link=/dev/ttyS11,echo=0
- the rust addon connects to
ttyS10
and writes to it - the test code connects with
node serialport
tottyS11
- shortly after when I attempt to read in ava from
ttyS11
I get this error message
FATAL ERROR: HandleScope::HandleScope Entering the V8 API without proper locking in place
1: 0xb02930 node::Abort() [node]
2: 0xa18149 node::FatalError(char const*, char const*) [node]
3: 0xcdceaa v8::Utils::ReportApiFailure(char const*, char const*) [node]
4: 0xcde40c v8::HandleScope::HandleScope(v8::Isolate*) [node]
5: 0xab7a86 napi_open_handle_scope [node]
6: 0x7f24abc1793b Poller::onData(uv_poll_s*, int, int) [/app/node_modules/@serialport/bindings-cpp/prebuilds/linux-x64/node.napi.glibc.node]
Now the rust napi native module that I am attempting to test does use threads, so I think the two somehow conflict, but I hardly grasp how.
Hello guys, so it is working now?
Doesn't look like it. I'm getting the same error:
npx ts-node src/index.ts
Worker Received: test
Serial baud rate: 115200
Record duration: 60
writing: ATZ
readResponse Timeout after 5 secs.
Error: Unable to reset factory default
writing: ATE1
FATAL ERROR: HandleScope::HandleScope Entering the V8 API without proper locking in place
1: 0xb7a940 node::Abort() [node]
2: 0xa8e72f node::FatalError(char const*, char const*) [node]
3: 0xd5c6aa v8::Utils::ReportApiFailure(char const*, char const*) [node]
4: 0xd5dc0c v8::HandleScope::HandleScope(v8::Isolate*) [node]
5: 0xb28b6a napi_open_handle_scope [node]
6: 0x7f95a42185eb Poller::onData(uv_poll_s*, int, int) [/home/foureight84/projects/node-callattendant/node_modules/@serialport/bindings-cpp/prebuilds/linux-x64/node.napi.glibc.node]
7: 0x16710a4 [node]
8: 0x165f4ce uv_run [node]
9: 0xabda6d node::SpinEventLoop(node::Environment*) [node]
10: 0xbc1164 node::NodeMainInstance::Run() [node]
11: 0xb35bc8 node::LoadSnapshotDataAndRun(node::SnapshotData const**, node::InitializationResult const*) [node]
12: 0xb3976f node::Start(int, char**) [node]
13: 0x7f95a796718a [/lib/x86_64-linux-gnu/libc.so.6]
14: 0x7f95a7967245 __libc_start_main [/lib/x86_64-linux-gnu/libc.so.6]
15: 0xabbdee _start [node]
Aborted
Interested in this
We landed napi support and support workers as of 2 years ago.
This still isn't working for me. I get the same error as this:
Doesn't look like it. I'm getting the same error:
npx ts-node src/index.ts Worker Received: test Serial baud rate: 115200 Record duration: 60 writing: ATZ readResponse Timeout after 5 secs. Error: Unable to reset factory default writing: ATE1 FATAL ERROR: HandleScope::HandleScope Entering the V8 API without proper locking in place 1: 0xb7a940 node::Abort() [node] 2: 0xa8e72f node::FatalError(char const*, char const*) [node] 3: 0xd5c6aa v8::Utils::ReportApiFailure(char const*, char const*) [node] 4: 0xd5dc0c v8::HandleScope::HandleScope(v8::Isolate*) [node] 5: 0xb28b6a napi_open_handle_scope [node] 6: 0x7f95a42185eb Poller::onData(uv_poll_s*, int, int) [/home/foureight84/projects/node-callattendant/node_modules/@serialport/bindings-cpp/prebuilds/linux-x64/node.napi.glibc.node] 7: 0x16710a4 [node] 8: 0x165f4ce uv_run [node] 9: 0xabda6d node::SpinEventLoop(node::Environment*) [node] 10: 0xbc1164 node::NodeMainInstance::Run() [node] 11: 0xb35bc8 node::LoadSnapshotDataAndRun(node::SnapshotData const**, node::InitializationResult const*) [node] 12: 0xb3976f node::Start(int, char**) [node] 13: 0x7f95a796718a [/lib/x86_64-linux-gnu/libc.so.6] 14: 0x7f95a7967245 __libc_start_main [/lib/x86_64-linux-gnu/libc.so.6] 15: 0xabbdee _start [node] Aborted