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

Resource temporarily unavailable Cannot lock port #2735

Open
cleoo opened this issue Dec 5, 2023 · 16 comments
Open

Resource temporarily unavailable Cannot lock port #2735

cleoo opened this issue Dec 5, 2023 · 16 comments

Comments

@cleoo
Copy link

cleoo commented Dec 5, 2023

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 cleoo changed the title Issue with SerialPort in Node.js: "Resource temporarily unavailable Cannot lock port" Resource temporarily unavailable Cannot lock port Dec 5, 2023
@devwatts
Copy link

Were you able to fix this issue?

@cleoo
Copy link
Author

cleoo commented Dec 23, 2023

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

@devwatts
Copy link

I am running raspbian on a pi 3

Getting the same issue on node 18

@robertsLando
Copy link

robertsLando commented Jan 19, 2024

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
Copy link

robertsLando commented Jan 22, 2024

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

@Theiremi
Copy link

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 !

@DasSamsi
Copy link

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!

@Theiremi
Copy link

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

@stknob
Copy link

stknob commented Feb 29, 2024

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
Copy link

stknob commented Feb 29, 2024

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:
nodejs/node@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): nodejs/node@686da19abb

@robertsLando
Copy link

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

@fcapano
Copy link

fcapano commented Mar 3, 2024

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

@stknob
Copy link

stknob commented Mar 4, 2024

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?

@fcapano
Copy link

fcapano commented Mar 12, 2024

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

@CorpusCallosum
Copy link

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
Copy link

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!

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

No branches or pull requests

8 participants