node-serialport
node-serialport copied to clipboard
Resource temporarily unavailable Cannot lock port
SerialPort Version
12.0.0
Node Version
v20.8.0
Electron Version
No response
Platform
Linux calimero 6.6.3-zen1-1-zen #1 ZEN SMP PREEMPT_DYNAMIC Wed, 29 Nov 2023 00:40:39 +0000 x86_64 GNU/Linux
Architecture
x64
Hardware or chipset of serialport
FTDI
What steps will reproduce the bug?
const { SerialPort } = require('serialport')
async function sleep(delay) {
await new Promise(r => setTimeout(r, delay))
}
async function open() {
return new Promise((resolve, reject) => {
const p = new SerialPort({
path: '/dev/ttyUSB1',
baudRate: 9600
}, err => {
if (err) return reject(err)
console.log('opened')
resolve(p)
}
)})
}
async function close(port) {
return new Promise((resolve, reject) => {
port.close(err => {
if (err) return reject(err)
console.log('closed')
resolve()
})
})
}
async function test() {
let p = await open()
p.on('data', data => console.log(data.toString()))
await sleep(5000)
await close(p)
p = null
await sleep(5000)
}
async function main() {
try {
await test()
await test()
}
catch(e) {
console.log(e.message)
}
}
main()
What happens?
on the second opening an error occurs, showing that the first connection, despite having been closed, still locks the port
If I remove p.on('data' ... )
from the test, the error does not appear, which shows that whatever port is used it is held by the process indefinitely. Same problem if I replace p.on('data', ... )
by p.read( ...)
.
did I miss something? Someone would have any idea ?
What should have happened?
The port, once closed, should have reopened without error.
Additional information
No response
Were you able to fix this issue?
The problem seems to come from a nodejs dependency. Versions higher than 20.2 have the defect. I got around the difficulty by downgrading all my installations to v20.2.0
I am running raspbian on a pi 3
Getting the same issue on node 18
Having the same problem with version 12.0.0 NodeJS 20.8.0. Simple repro script:
import { SerialPort } from 'serialport'
let port: SerialPort
function open() {
const path = '/dev/ttyUSB0'
if (!port) {
port = new SerialPort({
path,
baudRate: 9600,
dataBits: 8,
parity: 'none',
stopBits: 1,
autoOpen: false
})
port.on('error', err => {
console.log('Error: ', err.message)
})
port.on('close', () => {
console.log('Port closed')
})
port.on('open', () => {
console.log('Port opened')
})
// THIS CAUSES THE ERROR
port.on('data', data => {
console.log('Data: ', data)
})
}
port.open(err => {
if (err) {
return console.log('Error opening port: ', err.message)
}
setTimeout(() => {
close()
}, 3000)
})
}
function close() {
port.close(err => {
if (err) {
return console.log('Error closing port: ', err.message)
}
setTimeout(() => {
open()
}, 3000)
})
}
open()
I discovered that by removing the port.on('data')
listener the port is able to reconnect correctly, with that event listener I always get the error above: Error Resource temporarily unavailable Cannot lock port
cc @reconbot @GazHank
Seems NodeJS 18.19.0 (latest now) WORKS but 20.11.0 (latest now) still doesn't.
I encounter the same problem in my program and made some discoveries by executing the code of @robertsLando with debug flags. Here are the output I got in different scenarios :
- With
port.on('data')
:
serialport/stream _read queueing _read for after open +2ms
serialport/stream opened path: /dev/ttyUSB0 +13ms
Port opened
serialport/stream _read reading { start: 0, toRead: 65536 } +1ms
serialport/stream binding.read finished { bytesRead: 1 } +908ms
Data: <Buffer fa>
serialport/stream _read reading { start: 1, toRead: 65535 } +1ms
serialport/stream #close +2s
serialport/stream binding.close finished +0ms
Port closed
serialport/stream opening path: /dev/ttyUSB0 +3s
serialport/stream Binding #open had an error [Error: Error Resource temporarily unavailable Cannot lock port] +1ms
Error opening port: Error Resource temporarily unavailable Cannot lock port
- Without
port.on('data')
:
serialport/stream opened path: /dev/ttyUSB0 +11ms
Port opened
serialport/stream #close +3s
serialport/stream binding.close finished +1ms
Port closed
serialport/stream opening path: /dev/ttyUSB0 +3s
serialport/stream opened path: /dev/ttyUSB0 +9ms
Port opened
- Keeping
port.on('data')
but removing the delay between opening and closing :
serialport/stream _read queueing _read for after open +1ms
serialport/stream opened path: /dev/ttyUSB0 +9ms
Port opened
serialport/stream _read reading { start: 0, toRead: 65536 } +1ms
serialport/stream #close +3ms
serialport/stream binding.close finished +1ms
Port closed
serialport/stream binding.read finished { bytesRead: 1 } +907ms
Data: <Buffer f8>
serialport/stream _read queueing _read for after open +2ms
serialport/stream opening path: /dev/ttyUSB0 +2s
serialport/stream opened path: /dev/ttyUSB0 +10ms
Port opened
In the first output logs, it seems like :
- SerialPort tries to read data from the port (
serialport/stream _read reading { start: 0, toRead: 65536 }
) - Succesfully read data (
serialport/stream binding.read finished { bytesRead: 1 }
) - Tries to read more data (
serialport/stream _read reading { start: 1, toRead: 65535 }
) - Never stop waiting for new data even after closing the port
I tested with a device continuously sending data, and the problem haven't ocurred. In the last scenario, the port is probably closed before receiving the data, so SerialPort doesn't try to listen again for new data, and the port is completely free.
Hope this can help to solve the issue !
I had the same problem on Node v20.10.0.
I debugged 8+ hours with this simple Code:
import {SerialPort} from "serialport";
async function main() {
console.time();
let serialPort = new SerialPort({
path: '/dev/ttyS0',
baudRate: 9600,
autoOpen: false
});
serialPort.on("data", (data) => {
console.log("Data received: " + data);
});
try {
for (let i = 0; i < 3; i++) {
//OPEN
console.timeLog('default', 'open');
await new Promise((resolve, reject) => {
if (!serialPort.isOpen) serialPort.open((err) => {
if (err) return reject(err);
console.timeLog('default', 'serialPort opened');
return resolve();
});
});
await new Promise((resolve) => setTimeout(() => resolve(), 200));
//WRITE
console.timeLog('default', 'write');
await new Promise((resolve, reject) => {
if (serialPort.isOpen) serialPort.write('0010000202=?097\r', (err) => {
if (err) return reject(err);
console.timeLog('default', 'serialPort written');
return resolve();
});
});
await new Promise((resolve) => setTimeout(() => resolve(), 200));
//CLOSE
console.timeLog('default', 'close');
await new Promise((resolve, reject) => {
if (serialPort.isOpen) serialPort.close((err) => {
if (err) return reject(err);
console.timeLog('default', 'serialPort closed');
return resolve();
});
});
await new Promise((resolve) => setTimeout(() => resolve(), 200));
}
} catch (err) {
console.error(err);
// end the process
process.exit(1);
}
}
await main();
The solution for me was to upgrade Node to Version v21+ (v21.6.2). On 21+ it works like a dream and without clear the "data" listener!
Thank you @DasSamsi for your return ! Just tested on my side and cannot reproduce the bug with node V21.6.2
This seems to have been recently fixed in node 20.11.1 and 21.6.2 (i just tested a bunch of versions using @DasSamsi's reproducer), as everything prior to 21.6.2 is broken too.
The root cause seems to be related to libuv using io_uring in 20.11.0 and 21.6.1, which has been disabled in the newer node / libuv versions (node-v21.6.2/deps/uv update from 1.47.0 to 1.48.0):
$ grep ttyUSB node-v21.6.1.log -A1
437242 openat(AT_FDCWD, "/dev/ttyUSB0", O_RDWR|O_NOCTTY|O_NONBLOCK|O_SYNC|O_CLOEXEC <unfinished ...>
437232 io_uring_enter(37, 2, 2, IORING_ENTER_GETEVENTS, NULL, 0) = 2
vs.
$ grep ttyUSB node-v21.6.2.log -A1
437288 openat(AT_FDCWD, "/dev/ttyUSB0", O_RDWR|O_NOCTTY|O_NONBLOCK|O_SYNC|O_CLOEXEC <unfinished ...>
437281 epoll_ctl(31, EPOLL_CTL_ADD, 32, {events=EPOLLIN, data={u32=32, u64=32}}) = 0
UPDATE: First broken version is Node 20.3.0, which updates libuv to 1.45.0: https://github.com/nodejs/node/commit/bfcb3d1d9a
UPDATE #2: Libuv 1.48.0 update is unrelated, NodeJS commit that disabled io_uring by default (to fix the CVE-2024-22017 setuid() issue): https://github.com/nodejs/node/commit/686da19abb
@stknob Thanks for the reference, so this should work for every (latest) nodejs version as of today
I can still reproduce this bug in Arch Linux using NodeJs v21.6.2, serialport 12.0.0, and libuv 1.48.0,
I can still reproduce this bug in Arch Linux using NodeJs v21.6.2, serialport 12.0.0, and libuv 1.48.0,
@fcapano Does Arch set the UV_USE_IO_URING=1 environment variable somewhere?
@stknob no, doesn't look like it's set. But if I export UV_USE_IO_URING=0 before running the node server, I'm able to open/close/reopen ports. Thanks for the pointer!
Hello, I'm running Node v22.1.0, and still getting this error in my Electron app, when running in dev mode. Every time I make a change in my app and recompile - I'm getting this error:
Uncaught (in promise) Error: Error Resource temporarily unavailable Cannot lock port
Only way to fix it is to quit the app and relaunch in dev mode.
Any ideas how to fix this?
UV_USE_IO_URING=0
But if I export UV_USE_IO_URING=0 before running the node server, I'm able to open/close/reopen ports. Thanks for the pointer!
Hey @fcapano - could you please clarify? How do I go about exporting this environment variable? Thanks!
new SerialPort({
path: device.name,
baudRate: device.rate,
dataBits: device.data,
stopBits: device.stop,
parity: 'None',
flowControl: 'None',
autoOpen: false,
lock: false //try set lock to false
}, (err) => {
console.log(err, 'new err')
})
try set lock to false