node-serialport icon indicating copy to clipboard operation
node-serialport copied to clipboard

Resource temporarily unavailable Cannot lock port

Open cleoo opened this issue 1 year ago • 17 comments

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

cleoo avatar Dec 05 '23 18:12 cleoo

Were you able to fix this issue?

devwatts avatar Dec 23 '23 08:12 devwatts

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

cleoo avatar Dec 23 '23 13:12 cleoo

I am running raspbian on a pi 3

Getting the same issue on node 18

devwatts avatar Dec 24 '23 17:12 devwatts

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

robertsLando avatar Jan 19 '24 16:01 robertsLando

Seems NodeJS 18.19.0 (latest now) WORKS but 20.11.0 (latest now) still doesn't.

robertsLando avatar Jan 22 '24 09:01 robertsLando

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 :

  1. SerialPort tries to read data from the port (serialport/stream _read reading { start: 0, toRead: 65536 })
  2. Succesfully read data (serialport/stream binding.read finished { bytesRead: 1 })
  3. Tries to read more data (serialport/stream _read reading { start: 1, toRead: 65535 })
  4. 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 !

Theiremi avatar Jan 26 '24 22:01 Theiremi

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!

DasSamsi avatar Feb 28 '24 12:02 DasSamsi

Thank you @DasSamsi for your return ! Just tested on my side and cannot reproduce the bug with node V21.6.2

Theiremi avatar Feb 28 '24 18:02 Theiremi

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.

stknob avatar Feb 29 '24 12:02 stknob

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 avatar Feb 29 '24 12:02 stknob

@stknob Thanks for the reference, so this should work for every (latest) nodejs version as of today

robertsLando avatar Feb 29 '24 14:02 robertsLando

I can still reproduce this bug in Arch Linux using NodeJs v21.6.2, serialport 12.0.0, and libuv 1.48.0,

fcapano avatar Mar 03 '24 17:03 fcapano

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 avatar Mar 04 '24 12:03 stknob

@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!

fcapano avatar Mar 12 '24 21:03 fcapano

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?

CorpusCallosum avatar May 07 '24 17:05 CorpusCallosum

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!

CorpusCallosum avatar May 07 '24 17:05 CorpusCallosum

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

wwwmomo avatar Aug 03 '24 03:08 wwwmomo