Skip to content
This repository has been archived by the owner on Apr 22, 2023. It is now read-only.

Issue opening/closing character devices in Node.js #7101

Closed
bminer opened this issue Feb 11, 2014 · 37 comments
Closed

Issue opening/closing character devices in Node.js #7101

bminer opened this issue Feb 11, 2014 · 37 comments

Comments

@bminer
Copy link

bminer commented Feb 11, 2014

I am having issues opening/closing character devices in Node. How does one properly open and close a character device (i.e. a serial port or TTY device) for reading and writing?

Here's my little program that doesn't quite work as expected:

var fs = require("fs");
fs.open("/dev/ttyUSB0", "r+", function(err, fd) {
    if(err) throw err;
    var input = fs.createReadStream("/dev/ttyUSB0", {"fd": fd});
    input.on("data", function(chunk) {
        console.log("in:", chunk);
    });
    var out = fs.createWriteStream("/dev/ttyUSB0", {"fd": fd});
    console.log("out:", "ATZ\r\n");
    out.write("ATZ\r\n");
    setTimeout(function() {
        console.log("Closing file...");
        fs.close(fd, function(err) {
            console.log("File has been closed", err);
            // At this point, Node will just hang
        });
    }, 5000).unref();
});

The output of the program is something like this:

out: ATZ

in: <Buffer 41 54 5a 0a>
in: <Buffer 0a>
in: <Buffer 0a>
in: <Buffer 4f 4b 0a>
in: <Buffer 0a>
Closing file...
File has been closed null

Everything looks okay, but Node does not exit, as expected; rather, Node simply hangs at this point. Why? Is this a bug?

This problem exists even when opening a TTY terminal (i.e. /dev/tty2 for reading/writing)

@bminer
Copy link
Author

bminer commented Feb 11, 2014

Here is an even simpler program showing this bug:

var fs = require("fs");
var input = fs.createReadStream("/dev/tty2");
setTimeout(function() {
    console.log("Closing file...");
    input.pause();
    input.destroy();
    console.log("About to exit...");
    process.exit(0);
    console.log("This should not print.");
}, 5000);

In this case, the following output is printed:

Closing file...
About to exit...

Then Node hangs (it does not exit).

@rlidwka
Copy link

rlidwka commented Feb 13, 2014

Then Node hangs (it does not exit).

confirmed, bug seems to be present on both 0.10.21 and 0.11.11

indutny added a commit to indutny/node that referenced this issue Feb 13, 2014
zlib should not crash in `close()` if the write is still in progress.

fix nodejs#7101
@indutny
Copy link
Member

indutny commented Feb 13, 2014

Should be fixed by #7111

@indutny
Copy link
Member

indutny commented Feb 13, 2014

Though, I must admit that close() is internal undocumented method ;)

@indutny
Copy link
Member

indutny commented Feb 14, 2014

Found the source of the issue: it is because of the blocking nature of ttys in libuv. Don't know about v0.10, but v0.11 for sure blocks in a read() call in a thread pool, while main thread blocks in uv_thread_join(): waiting for worker IO threads to terminate.

If you'll hit enter - it should exit. I'll see what I could do to fix it.

@bminer
Copy link
Author

bminer commented Feb 15, 2014

@indutny - Thanks for the update. Why are worker I/O threads blocked on a read() call after the file or I/O stream has been closed? In other words, why doesn't the read() call fail once the file descriptor is closed / cleaned up?

I don't know if I quite understand how read() works in Linux...

@indutny indutny reopened this Feb 18, 2014
@indutny
Copy link
Member

indutny commented Feb 18, 2014

@bminer after some consideration, I came to conclusion that this is incorrect way to read from a character device. This kind of thing isn't actually a file and may block for a considerable long time if you would wish to read from it. The proper way would be to either instantiate http://nodejs.org/api/tty.html#tty_class_readstream or http://nodejs.org/api/net.html#net_new_net_socket_options with the result of fs.open as an fd option.

@indutny indutny closed this as completed Feb 18, 2014
@bminer
Copy link
Author

bminer commented Feb 26, 2014

@indutny - I figured as much; however, there is no clear Node API to read from character devices at this time...

What are the next steps?

@indutny
Copy link
Member

indutny commented Feb 26, 2014

Have you tried creating new net.Socket({ fd: fs.open('/dev/...') })?

@tjfontaine
Copy link

probably want openSync just to be clear

@indutny
Copy link
Member

indutny commented Feb 26, 2014

oh, that's right, sorry.

@bminer
Copy link
Author

bminer commented Feb 26, 2014

@indutny - Using new net.Socket({ fd: fs.open('/dev/...') }) causes an Error:

net.js:50
  throw new TypeError('Unsupported fd type: ' + type);
        ^
TypeError: Unsupported fd type: TTY
    at createHandle (net.js:50:9)
    at new Socket (net.js:156:20)
    at Object.<anonymous> (/node_issue_7101.js:4:14)
    at Module._compile (module.js:456:26)
    at Object.Module._extensions..js (module.js:474:10)
    at Module.load (module.js:356:32)
    at Function.Module._load (module.js:312:12)
    at Function.Module.runMain (module.js:497:10)
    at startup (node.js:119:16)
    at node.js:901:3

Node version 0.10.22

@bminer
Copy link
Author

bminer commented Feb 26, 2014

Also, I should mention that I've tried the http://nodejs.org/api/tty.html#tty_class_readstream route to no avail. I'm not quite sure how this would work either since the APIs aren't really exposed.

@indutny
Copy link
Member

indutny commented Feb 26, 2014

Oh, right what about var t = new tty.ReadStream(fs.openSync('/dev/tty', 'r')) ? Seems to be working fine for me.

@indutny
Copy link
Member

indutny commented Feb 26, 2014

But I see what you mean, documentation is incomplete on this topic. Would you mind submitting issue or pull request about that?

@bminer
Copy link
Author

bminer commented Feb 26, 2014

@indutny - Yeah! Actually, that works great! Problem is the API docs, I suppose...

A net.Socket subclass that represents the readable portion of a tty. In normal circumstances, process.stdin will be the only tty.ReadStream instance in any node program (only when isatty(0) is true).

@bminer
Copy link
Author

bminer commented Feb 26, 2014

Also... what is the difference between tty.ReadStream and tty.WriteStream? Why are there two classes when they both inherit from net.Socket? Sockets are readable and writable. In fact, writing to the tty.ReadStream socket actually writes to the character device.

@indutny
Copy link
Member

indutny commented Feb 26, 2014

Readable are non-blocking and writable are blocking. I think ReadStream may be fine for now. Would you mind submitting doc fix?

@bminer
Copy link
Author

bminer commented Feb 26, 2014

@indutny - Sure, I can fix the docs, but what about writing to the character device? If I use tty.WriteStream, I get something like:

node: ../deps/uv/src/unix/core.c:701: uv__io_stop: Assertion `loop->watchers[w->fd] == w' failed.
Aborted

Alternatively, I can call .write(...) on the tty.ReadStream and it will write to the character device just fine. Any idea why?

@bminer
Copy link
Author

bminer commented Feb 26, 2014

Just thinking... could this be because I am using tty.ReadStream and tty.WriteStream on the same file descriptor? I just opened the device once using "r+" flags. Thoughts?

@indutny
Copy link
Member

indutny commented Feb 26, 2014

Its because you are using the same fd for both of them.

@indutny
Copy link
Member

indutny commented Feb 26, 2014

I guess we should probably dup fd, @saghul thoughts?

@bminer
Copy link
Author

bminer commented Feb 26, 2014

Also, it would appear that Node will hang if only the file descriptor is closed. For example:

var fd, socket = new tty.ReadStream(fd = fs.openSync("/dev/tty2", "r") );
setTimeout(fs.closeSync.bind(fs, fd), 1000);
// Node will hang

As opposed to:

var fd, socket = new tty.ReadStream(fd = fs.openSync("/dev/tty2", "r") );
setTimeout(socket.destroy.bind(socket), 1000);
// Node will not hang and will exit properly

I don't think that the same happens for normal files. What are your thoughts on this? Perhaps, closing the fd of a character device should cause the Readable stream to get EOF.

@saghul
Copy link
Member

saghul commented Feb 26, 2014

I guess we should probably dup fd, @saghul thoughts?

Not sure it that's our silver bullet. The same limitation applies to poll handles, FWIW. About the blocking-ness issue, we need to think about how to solve this The Right Way (TM) once 0.12 is out, IMHO.

@indutny
Copy link
Member

indutny commented Feb 27, 2014

@saghul at least we need a concept of both readable/writable tty.

@bminer
Copy link
Author

bminer commented Mar 7, 2014

@indutny - What's this status of this issue? Any chance that this could be re-opened?

@indutny indutny reopened this Mar 7, 2014
@indutny
Copy link
Member

indutny commented Mar 7, 2014

Ok, if you insist :)

@bminer
Copy link
Author

bminer commented Apr 1, 2014

Hello again. I wanted to follow up on this issue to see what I can do to help. I'd like to document the proper way to interact with character devices. Is this the best way?

const CHAR_DEVICE = "/dev/tty2";
var fs = require("fs")
    , tty = require("tty");
var input = new tty.ReadStream(fs.openSync(CHAR_DEVICE, "r") );
input.setRawMode(true);
var output = new tty.WriteStream(fs.openSync(CHAR_DEVICE, "w") );
input.on("data", function(chunk) {
    console.log("Read chunk from CHAR_DEVICE:", chunk);
});
output.write("This is a test!\n");
setTimeout(function() {
    console.log("Closing file...");
    input.end();
    output.end();
    console.log("Exiting...");
}, 5000);

The above seems to work OK to me. If this is the preferred method of talking to a TTY, I think that the API docs may need a bit of work. I'd be willing to re-work them and open a PR. Please let me know your thoughts. Thanks!

EDIT: Also, while I'm thinking about this, what is the preferred way to set TTY options on a serial port (baud rate or partity, for example)? In Linux, Node could spawn stty or something... is there a decent cross-platform solution?

@saghul
Copy link
Member

saghul commented Apr 1, 2014

That looks ok to me. It still looks a bit weird to me that we need to use fs.openSync for a "non fs operation", we just need the fd. The idea of doing stat on uv_fs_open and returning an error if the path isn't a regular file crossed my mind, but hell would break loose, it seems :-(

EDIT: Also, while I'm thinking about this, what is the preferred way to set TTY options on a serial port (baud rate or partity, for example)? In Linux, Node could spawn stty or something... is there a decent cross-platform solution?

We don't have a way to do that currently.

@bminer
Copy link
Author

bminer commented May 2, 2014

Hello again. I found out that tty.WriteStream blocks the main event loop in Node while writing. If you're writing a lot of data in one sitting, that can be a few seconds of blocking.

Does that sound right? And if so, is there any way to write data to a TTY in a separate thread? Can I use the tty.ReadStream instance to write to the TTY instead?

For me, I am writing a lot of data to a serial port at 9600 baud... this causes my whole web server to freeze for a bit. Connection timeouts occur, etc. No good.

@indutny
Copy link
Member

indutny commented May 2, 2014

I think it should be no longer a problem with v0.11 release. Could you please give it a try?

@misterdjules
Copy link

@bminer Is that still an issue with the latest 0.11 release?

@bminer
Copy link
Author

bminer commented Nov 20, 2014

@misterdjules - I don't know what I don't know.
@indutny - What do you believe has been fixed in latest 0.11? Do filesystem read/write calls and network operations (i.e. DNS lookups) use separate threads now? Or (better question), is it possible to safely read/write to a serial port/TTY in Node 0.11 in a non-blocking way?

@misterdjules
Copy link

@bminer I had read the whole (long) thread too quickly while triaging issues, and after another read, I realize that my question doesn't help. I'm sorry for the confusion.

I don't have an answer to "what would be the best way" to achieve what you're trying to do, but I'm going to try to give you some pointers that may help you answer some of your other questions.

In node's v0.12 branch, it seems that writing to a tty.writeStream is not a blocking operation, unless the current terminal cannot be reopened. This logic seems a bit bogus to me, since the file descriptor in uv_tty_init does not necessarily point to /dev/tty (like in your case when you're creating a tty.WriteStream to write to a serial TTY). You might want to check if your tty.WriteStream's underlying fd is incorrectly set to non-blocking mode there.

Another thing that could lead to a big write is that when writing a string to a buffer through a tty.WriteStream, node will first try to write immediately if the data to write isn't too big. In your case, if you're writing less than 16KB of data, but still a large number of bytes, on a slow serial connection, it could lead to some delay.

I'm not too familiar though with this part of the code base yet, so I would take my suggestions with a grain of salt. It could be an interesting avenue to explore.

It should be possible to a write to a TTY in a separate thread. One way to do that would be to write a binary node add-on that uses libuv's thread pool to write to it. However, using a fs.WriteStream instance to write to your TTY (like you showed us in one of your previous comments) would basically achieve that, since fs.WriteStream operations run on libuv's thread pool.

Regarding your question about file system operations, they happen on libuv's thread pool, unless one uses the "sync" variants (e.g fs.readFileSync).

DNS lookups (if we're talking about dns.lookup and not dns.resolve*) also happen on libuv's thread pool. I've recently created a PR that tries to improve the dns' module documentation by adding some information about this specific topic.

I hope it helps!

@saghul
Copy link
Member

saghul commented Nov 21, 2014

This might be relevant here: joyent/libuv#1580

@bminer
Copy link
Author

bminer commented Dec 3, 2014

Interesting... I don't know enough about this topic, so I'm going to hang back and wait for things to transpire. I'm hoping that would be cool with everyone.

I'm just a measly app developer who wants a Node.js serial port library that works well.

In addition, I'm griping here because file system I/O is shared with network I/O on the same thread pool. To me, that seems rather foolish since poor network connectivity can translate to slow/unresponsive file system I/O (see issue #2868).... and vice versa.

@jasnell
Copy link
Member

jasnell commented Jun 24, 2015

Closing due to lack of activity and it's not clear if there's anything actually to do. Can reopen again if necessary

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

No branches or pull requests

7 participants