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

readline with /dev/tty: echos and doesn't close #21319

Open
coolaj86 opened this issue Jun 13, 2018 · 11 comments
Open

readline with /dev/tty: echos and doesn't close #21319

coolaj86 opened this issue Jun 13, 2018 · 11 comments
Labels
readline Issues and PRs related to the built-in readline module. tty Issues and PRs related to the tty subsystem.

Comments

@coolaj86
Copy link
Contributor

coolaj86 commented Jun 13, 2018

  • Version: v10.2.1
  • Platform: Linux and macOS
  • Subsystem: readline

The use case is that a node script is being run from a piped bash script and so process.stdin is the pipe and /dev/tty must be used explicitly to get user input.

var readline = require('readline');
var input = require('fs').createReadStream('/dev/tty');
var rl = readline.createInterface({
  input: input
, output: process.stdout
//, terminal: false
});
rl.question("This is the prompt: ", function (response) {
  console.log("This is the response:", response);
  rl.close();
  // If I don't close the input explicitly, it stays open.
  input.close();
});

Output:

This is the prompt: yay
yay
This is the response: yay

Uncommenting terminal: false solves the issue of echoing, but creates a new problem in that it no longer treats stdout as a proper terminal.

Past issues that appear to be similar: #7965 nodejs/node-v0.x-archive#7101 https://stackoverflow.com/questions/24661774/createinterface-prints-double-in-terminal

@gireeshpunathil
Copy link
Member

(lldb) bt
* thread #10
  * frame #0: 0x00007fff62f2214a libsystem_kernel.dylib`read + 10
    frame #1: 0x000000010092f2b6 node`uv__fs_buf_iter + 78
    frame #2: 0x000000010092cf29 node`uv__fs_work + 358
    frame #3: 0x0000000100929755 node`worker + 95
    frame #4: 0x00007fff630e8661 libsystem_pthread.dylib`_pthread_body + 340
    frame #5: 0x00007fff630e850d libsystem_pthread.dylib`_pthread_start + 377
    frame #6: 0x00007fff630e7bf9 libsystem_pthread.dylib`thread_start + 13
(lldb) thr sel 1
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
    frame #0: 0x00007fff62f21bf2 libsystem_kernel.dylib`kevent + 10
libsystem_kernel.dylib`kevent:
->  0x7fff62f21bf2 <+10>: jae    0x7fff62f21bfc            ; <+20>
    0x7fff62f21bf4 <+12>: movq   %rax, %rdi
    0x7fff62f21bf7 <+15>: jmp    0x7fff62f17b00            ; cerror_nocancel
    0x7fff62f21bfc <+20>: retq   
(lldb) bt
* thread #1, queue = 'com.apple.main-thread', stop reason = signal SIGSTOP
  * frame #0: 0x00007fff62f21bf2 libsystem_kernel.dylib`kevent + 10
    frame #1: 0x000000010093abbb node`uv__io_poll + 836
    frame #2: 0x000000010092b88d node`uv_run + 315
    frame #3: 0x000000010003415c node`node::Start(v8::Isolate*, node::IsolateData*, int, char const* const*, int, char const* const*) + 780
    frame #4: 0x0000000100033c23 node`node::Start(uv_loop_s*, int, char const* const*, int, char const* const*) + 442
    frame #5: 0x00000001000333ba node`node::Start(int, char**) + 390
    frame #6: 0x0000000100001034 node`start + 52
(lldb) 

Basically the reader thread is engaged in reading from the device, and is blocked, immune to the stream close. This is closely related to #15439 and a documentation PR detailing the limitation is in progress through #21212

input.close seem to be working in the code you provided but I don't know why it should, as the below code hangs too.

var input = require('fs').createReadStream('/dev/tty');
setTimeout(() => {
input.close()
}, 1000)

pushing a null character into the device stream helps to unblock the reader thread.

@@ -8,6 +8,7 @@ var rl = readline.createInterface({
 rl.question("This is the prompt: ", function (response) {
   console.log("This is the response:", response);
   rl.close();
+  input.push(null);
   // If I don't close the input explicitly, it stays open.
   // input.close();
 });

Hope this helps.

@coolaj86
Copy link
Contributor Author

coolaj86 commented Jun 14, 2018

Thanks.

To your point, I found that the order in which readline and the input are closed seems to matter:

rl.question("This is the prompt: ", function (response) {
  console.log("This is the response:", response);
  // If I close the input first, it still stays open.
  input.close();
  rl.close();
});

It may just be a goldilocks timing issue - or maybe the use of terminal: false changes it too. I haven't tried every permutation.

I'll try your .push(null) and see how that works for me.

@addaleax
Copy link
Member

@coolaj86 Aside: You would probably rather want to use a TTY stream rather than an fs stream for accessing the TTY? That way you don’t need to worry about blocking reads

@coolaj86
Copy link
Contributor Author

coolaj86 commented Jun 14, 2018

@addaleax I'm not quite sure how that works. The docs talk a lot about process.stdin and process.stdout, but they don't actually give any examples of how to use tty.

Based on playing around in the repl I'm guessing I might use it by doing something like this?

var fd = fs.openSync('/dev/tty', 'r');
var input = tty.ReadStream(fd);

Could I somehow use tty on Windows too?

@addaleax
Copy link
Member

@coolaj86 Yes, exactly. :)

Could I somehow use tty on Windows too?

I don’t know about that. @nodejs/platform-windows

@bzoz
Copy link
Contributor

bzoz commented Jun 14, 2018

I guess fs.openSync('conin$', 'r') would be something similar on Windows, but it fails with ENOENT

@Fishrock123
Copy link
Contributor

Fishrock123 commented Jun 15, 2018

var fd = fs.openSync('/dev/tty', 'r');
var input = tty.ReadStream(fd);

This code seems correct to me, did that help @coolaj86?

Could I somehow use tty on Windows too?

So long as you know the stdin handle, I think so.

@Fishrock123 Fishrock123 added readline Issues and PRs related to the built-in readline module. tty Issues and PRs related to the tty subsystem. labels Jun 15, 2018
@coolaj86
Copy link
Contributor Author

coolaj86 commented Jun 24, 2018

@Fishrock123 Yes, that is the correct way to do it on Mac and Linux.

@gireeshpunathil I've tried a few different ways but can't tell if input.push(null) has any affect.

It seems like node is now properly exiting, but leaving /dev/tty in a bad state. The shell will just hang until I hit enter and then the bash script that was calling the node script continues.

For now I just print "Press any key to continue..." before calling process.exit(0) and then once enter is hit everything goes as expected.

@bnoordhuis
Copy link
Member

It seems like node is now properly exiting, but leaving /dev/tty in a bad state. The shell will just hang until I hit enter and then the bash script that was calling the node script continues.

You didn't mention how exactly you (or bash) start node, but if it's running in the background, then opening /dev/tty without the fs.constants.O_NOCTTY flag turns it into a controlling terminal. That's unlikely to be what you want.

@coolaj86
Copy link
Contributor Author

I added fs.constants.O_NOCTTY and it did not change the behavior.

For context

I made a convenient curl | bash installer to bootstrap the install process of my app:

curl -fsSL https://get.example.app | bash

When that script runs it installs an exact version of node (v10.2.1 presently as other versions we tested had particular bugs that broke the app) to a special prefix directory and (unzip / tar / gunzip)s the an exact version (tag) of the app from a git repo also into that same directory.

installer.sh:

#!/bin/bash

# do some stuff to install node
# do some stuff to unzip / untar our app
# generate a bash file to call the app

/path/to/app/bin/app --init --some options
/path/to/app/bin/app

The script generates and then calls another script which calls that version of node and the app, passing any options.

/path/to/bin/app:

#!/bin/bash
/path/to/app/bin/node /path/to/app/bin/app.js "$@"

Where it gets stuck

# This is where it needs some additional input from the user to finish the install
/path/to/app/bin/app --init --some options

# That hangs and it doesn't progress to here:
/path/to/app/bin/app

We're now talking about 2 separate but related issues. I'll try to come up with a test case to show this second issue as well.

@Trott
Copy link
Member

Trott commented Mar 15, 2021

Hi, @coolaj86. I know this issue has been quiet for a very long time and it's possible you're not working on the relevant code base anymore. Which of the issues described here are you still experiencing (if any)?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
readline Issues and PRs related to the built-in readline module. tty Issues and PRs related to the tty subsystem.
Projects
None yet
Development

No branches or pull requests

7 participants