Opening a serial port from within a worker thread fails with:
Error: Module did not self-register.
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.");
}
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:
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.
Most helpful comment
Got to start moving to NAPI anyway 🤷♂️