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

Double defined line object, cannot call getValue or setValue funciton. #4

Closed
TaylorIsAGoodboy opened this issue Apr 13, 2022 · 26 comments
Labels
bug Something isn't working enhancement New feature or request

Comments

@TaylorIsAGoodboy
Copy link

Hi, Leonardo
I defined line twice, then it occured "Error: Unable to get value for this line".
Is there some way to this error.
I make a nodered node which based on your node-libgpiod library. The first time is good,then move debug node and deploy again, error occurs. I think the reason it i double defined line object.

image
image

@sombriks
Copy link
Owner

Hello @TaylorIsAGoodboy !

you mean if, for some reason, same line gets defined twice during code execution, it will fail?

please provide a small project sampling the situation so i can try it on the hardware available here.

@TaylorIsAGoodboy
Copy link
Author

I wrote a nodered node based on your library. I depoyed flow, it worked ok. But when I modified something, and depoyed again it shows error. Error may be "unable to get value for this line", "Segment fault" or "double free".

Url of nodered node is https://github.com/TaylorIsAGoodboy/nodered-gpiod-node

@TaylorIsAGoodboy
Copy link
Author

Hello @TaylorIsAGoodboy !

you mean if, for some reason, same line gets defined twice during code execution, it will fail?

please provide a small project sampling the situation so i can try it on the hardware available here.

You can run the script below at two different term.

const { version, Chip, Line } = require("node-libgpiod");

let chip = new Chip(2);
let line = new Line(chip, 7); 

line.requestOutputMode();

const blink = function () {
  // avoid early gc
  this.chip = chip
  this.line = line
  let val = this.line.getValue();
  this.line.setValue(1-val);
  setTimeout(blink, 1000);
};

setTimeout(blink, 1000);

Do we have a way to release the line before we call new Line() to define the line?

@sombriks
Copy link
Owner

Hello @TaylorIsAGoodboy, i just merged a contributed patch and will make another release!
Once it done please let me know if it is solved.

@dmitrydvorkin
Copy link
Contributor

Hello, @TaylorIsAGoodboy

Do we have a way to release the line

obviously, yes.
In my gpio module for nodeRed (will be published within 2 days) I am doing like this:

node.on('close', function (removed, done) {
  node.line.release()
  done()
})

It is described in documentation:
https://nodered.org/docs/creating-nodes/node-js#closing-the-node

It helps alot.

BTW, if you'll do
let line = new Line(chip, 7);
line = new Line(chip, 7);

obviously, descrtructor for the first object has to be called at the second line right before "line" object will be set to the new value. I think, It fails at desctructor. Check it with my update. If you'll experience the same problem just enable the debugging at chip.hh and see the calls sequence

@noctarius
Copy link
Collaborator

I guess it'd be possible to cache the line objects internally, but that would cause plenty of other issues. From my perspective the issue is not two line objects, but both calling requestXMode. Is there any way in node-red to cache / provide objects to be shared between multiple node instances?

My guess would be, that the event handler for close runs to late (see nodejs event handling) and the second node is executed before the close event was handled (though the first line object isn't released yet).

@dmitrydvorkin
Copy link
Contributor

dmitrydvorkin commented Apr 16, 2022

Dear @noctarius !
You commit is very good. But things like
getNumberOfLines, getChipName, getChipLabel
are libgpiod-specific :) We could get rid of libgpiod dependency and simply use native linux functions. It could even simplify the coding :)

also some things are useless. For example, if you get number of pins = 200 it means nothing - 80% of them could be preallocated and not available.
chipName and chipLabel are SoC- and driver-specific. I suspect, most implementations are simply returns just the pinmux driver name.

The really useful thing that should be added is the way to find out the list of all GPIO chips available.
Most SoCs has 1 GPIO chip. But some has 2 or 4, each contains from 16 to 32 or 64 pins.

@TaylorIsAGoodboy
Copy link
Author

Hello @TaylorIsAGoodboy, i just merged a contributed patch and will make another release! Once it done please let me know if it is solved.

OK, I will test it tommorrow. Thank you for your efforts!

@TaylorIsAGoodboy
Copy link
Author

Hello @TaylorIsAGoodboy, i just merged a contributed patch and will make another release! Once it done please let me know if it is solved.

Hi @sombriks , the issue still existed.
image

@TaylorIsAGoodboy
Copy link
Author

Dear @noctarius ! You commit is very good. But things like getNumberOfLines, getChipName, getChipLabel are libgpiod-specific :) We could get rid of libgpiod dependency and simply use native linux functions. It could even simplify the coding :)

also some things are useless. For example, if you get number of pins = 200 it means nothing - 80% of them could be preallocated and not available. chipName and chipLabel are SoC- and driver-specific. I suspect, most implementations are simply returns just the pinmux driver name.

The really useful thing that should be added is the way to find out the list of all GPIO chips available. Most SoCs has 1 GPIO chip. But some has 2 or 4, each contains from 16 to 32 or 64 pins.

Hi @dmitrydvorkin, If your nodered node was ready, please share me the url, thank you.
My node still has issues, when i add release() funciton in 'close' event, it shows timeout.
image

sombriks added a commit that referenced this issue Apr 19, 2022
@sombriks
Copy link
Owner

@TaylorIsAGoodboy i am building a test case for your specific issue, don't know if it helps but create several instances of lines are ok, but for some reason requestOutputMode more than once kicks the Error. still early steps here but i'll investigate more.

Captura de tela de 2022-04-18 22-59-47

@sombriks
Copy link
Owner

we might just need to know current line direction before request intput or output mode
https://git.kernel.org/pub/scm/libs/libgpiod/libgpiod.git/tree/lib/helpers.c?h=v1.6.x

let's look for some useful call in the source.

@TaylorIsAGoodboy
Copy link
Author

TaylorIsAGoodboy commented Apr 19, 2022 via email

@sombriks
Copy link
Owner

Yes, totally agree!

@noctarius
Copy link
Collaborator

I think the issue is a different one. From my perspective it's not about the direction, but you cannot reserve a line twice, that said, the previous reservation needs to be released before a new one can be acquired. It's luckily not a US airline carrier overbooking their flights ;-)

@dmitrydvorkin
Copy link
Contributor

dmitrydvorkin commented Apr 20, 2022

@noctarius @sombriks !

if you'd run the code in debug mode (I added it in my commit) you can see:
../src/chip.cc New():31
../src/chip.cc New():33
../src/chip.cc Chip():15
../src/chip.cc Chip():17 0x207b2e8
../src/chip.cc New():36 0x212e198
../src/chip.cc New():38 0x212e198
../src/chip.cc New():48
../src/line.cc New():37
../src/line.cc New():39
../src/line.cc Line():21
../src/chip.cc getNativeChip():52 0x207b2e8
../src/line.cc Line():23 0x2094d70
../src/line.cc New(12):43 0x207b2b8
../src/line.cc New(12):45 0x207b2b8->0x2094d70
../src/line.cc New():55
../src/line.cc New():37
../src/line.cc New():39
../src/line.cc Line():21
../src/chip.cc getNativeChip():52 0x207b2e8
../src/line.cc Line():23 0x2094d70
../src/line.cc New(12):43 0x207b2d0
../src/line.cc New(12):45 0x207b2d0->0x2094d70
../src/line.cc New():55
../src/line.cc ~Line():28
../src/line.cc ~Line():30
../src/line.cc ~Line():32
1.4.3
../src/line.cc requestOutputMode():93
../src/line.cc requestOutputMode():108 0x207b2d0
../src/line.cc getNativeLine():126 0x2094d70
/home/root/exp/node-red-contrib-libgpiod/xx.js:9
line.requestOutputMode();
^

Error: ::requestOutputMode() failed


do you see
../src/line.cc New(12):45 0x207b2b8->0x2094d70
../src/line.cc New(12):45 0x207b2d0->0x2094d70
?
I've created two Line objects with same pin#.
Yes, it is two different objects, but THE struct gpiod_line * pointer returned by gpiod_chip_get_line() IS THE SAME?
Why?
We have same Chip object for both Line objects and please, take a look at code of the gpiod_chip_get_line() there:
https://github.com/brgl/libgpiod/blob/cc23ef61f66d5a9055b0e9fbdcfe6744c8a637ae/lib/core.c#L291

It simply returns the reference to previously allocated gpiod_line struct.
And right before we do .getValue() or .setValue() the destructor for the first Line object is called.
Destructor closes the line handle and free its resources inside LibGPIOD.
so any next calls that is using the reference to this gpiod_line * FAIL.

Obviously we can't call gpiod_line_close_chip(line); till we have copy of this object in some other Line instance.

How to do that correctly in NodeJS? I don't know.

I'm thinking about some reference counter or thomething like this. This is the task for man who have good experience in NodeJS internals.

@noctarius
Copy link
Collaborator

Do you have some test code? Normally, to prevent an object from being GC'd (and hence deconstructed) you need to hold a hard reference to the object. For example by keeping a module local cache of all loaded chips (or by keeping it referenced in some object) and export a function which uses that cache. No need to sync for concurrency, since nodejs doesn't have concurrency (do to the event loop).

This is basically what I do with our Port classes. And when creating a port, I reuse previously chip instances. That way you prevent the chip object from not being referenced and garbage collected.

class Port {
  #chip
  #line

  constructor(chip: Chip, offset: number) {
    this.#chip = chip; // DO NOT DELETE to prevent it from being GC'd
    this.#line = newLine(chip, offset);
  }
}

function newPort(...) {
  const chips: Array<Chip> = [];
  let chip = chips[gpiochip];
  if (chip === undefined) {
    chip = newChip(gpiochip);
    chips[gpiochip] = chip;
  }
  return newPort(chip, gpio)
}

@dmitrydvorkin
Copy link
Contributor

dmitrydvorkin commented Apr 21, 2022

test code:

const { version, Chip, Line } = require("node-libgpiod");

const chip = new Chip(0);
var line = new Line(chip, 12); // led on GPIO12
line = new Line(chip, 12); // led on GPIO12
let count = 10;

console.log(version());
line.requestOutputMode();

const blink = () => {
  if(count){
    line.setValue(count-- % 2);
    setTimeout(blink,1000);
  } // else line.release(); 
  // not needed, libgpiod releases resources on process exit  
};

setTimeout(blink,1000);

@noctarius , I think it should be fixed with
ObjectReference
https://github.com/nodejs/node-addon-api/blob/main/doc/object_reference.md
or
https://stackoverflow.com/questions/34175830/how-to-implement-reference-counting-in-javascript

@noctarius
Copy link
Collaborator

noctarius commented Apr 21, 2022

Haven't tested / really looked at it yet. Just making sure, NaN is a different C API adapter than Napi. Not sure you can mix and match them, also not really a nodejs developer :)

edit: seems like https://github.com/nodejs/nan/blob/main/doc/persistent.md NaN offers a similar functionality with NaN::Persistent

@TaylorIsAGoodboy
Copy link
Author

test code:

const { version, Chip, Line } = require("node-libgpiod");

const chip = new Chip(0);
var line = new Line(chip, 12); // led on GPIO12
line = new Line(chip, 12); // led on GPIO12
let count = 10;

console.log(version());
line.requestOutputMode();

const blink = () => {
  if(count){
    line.setValue(count-- % 2);
    setTimeout(blink,1000);
  } // else line.release(); 
  // not needed, libgpiod releases resources on process exit  
};

setTimeout(blink,1000);

@noctarius , I think it should be fixed with ObjectReference https://github.com/nodejs/node-addon-api/blob/main/doc/object_reference.md or https://stackoverflow.com/questions/34175830/how-to-implement-reference-counting-in-javascript

Yes, it may be the reason. I find the c libray to call ioctl to operate gpio. Is it difficulty writing a pure nodeJs library?
I find there is a python library which doesnot depend c library. The address is https://pypi.org/project/gpiod/.

And there is a ioctl library, we may create a pure js libgpiod based on this.
The address is https://www.npmjs.com/package/ioctl.

@dmitrydvorkin
Copy link
Contributor

dmitrydvorkin commented Apr 22, 2022

with C library and direct ioctl commands you'll have same issue, but with "int fd" (file descriptor).
if you're not able to have only 1 NodeJS object for 1 line, you'll have "race condition" problem.
So line object has to be unique globally.
Same rule for Chip object.

there are should be the way to do it in NodeJS

I have an idea:
Create one global "Lines" object and use it as sigletone.
When you want top access to certain line you should call
Lines.setValue( linenum, val)

counstructor should be
Lines( Chip &_chip).

object Chips should be signletone too with method to get certain chip, like:
let chip = Chips.get( "gpiodev0")

@sombriks
Copy link
Owner

i think NAN will help us moving to global/persistent instances
https://github.com/nodejs/nan#persistent-references

will try something later

@dmitrydvorkin
Copy link
Contributor

it should be like this:
every /dev/gpiochip* should have 1 Lines object.
when you want to access certain line, you just call
Lines.somefunc( pinnum, params)

thats all!

@TaylorIsAGoodboy
Copy link
Author

i think NAN will help us moving to global/persistent instances https://github.com/nodejs/nan#persistent-references

will try something later

OK, If ok, please tell me. I will test. Thank you.

@sombriks sombriks added bug Something isn't working enhancement New feature or request labels Nov 15, 2023
sombriks added a commit that referenced this issue Jun 30, 2024
@sombriks
Copy link
Owner

added a testcase for the double definition issue.

basically we can define and redefine a line as many times we want as long we release the line.

pin is a limited resource so i think it makes sense.

and finally we have a few instructions on how to run the gpio-sim, that might help to understand the issue furthermore.

for now i am closing this issue because error handling either got better or libgpiod itself evolved since then.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working enhancement New feature or request
Projects
None yet
Development

No branches or pull requests

4 participants