Skip to content

tty: stdio properties are undefined inside exec() child #2333

New issue

Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? # to your account

Closed
silverwind opened this issue Aug 9, 2015 · 31 comments
Closed

tty: stdio properties are undefined inside exec() child #2333

silverwind opened this issue Aug 9, 2015 · 31 comments
Labels
doc Issues and PRs related to the documentations. tty Issues and PRs related to the tty subsystem.

Comments

@silverwind
Copy link
Contributor

I'm wondering if this is intentional or a bug:

parent.js

var exec = require("child_process").exec;

exec("iojs child.js", function (err, stdout, stderr) {
  process.stdout.write(stdout);
});

child.js

console.log(process.stdin.isTTY);
console.log(process.stdin.isRaw);
console.log(process.stdin.setRawMode);
console.log(process.stdout.columns);

output:

undefined
undefined
undefined
undefined

ref: sindresorhus/grunt-shell#95 raineorshine/npm-check-updates#119

@silverwind silverwind added child_process Issues and PRs related to the child_process subsystem. tty Issues and PRs related to the tty subsystem. labels Aug 9, 2015
@thefourtheye
Copy link
Contributor

When the new process is created, the process.stdin is actually a PIPE and it doesn't have isTTY, isRaw and setRawMode defined. We can actually fix them like this, I think

diff --git a/src/node.js b/src/node.js
index af66b17..87121bc 100644
--- a/src/node.js
+++ b/src/node.js
@@ -717,6 +717,12 @@
         stdin._handle.readStop();
       });

+      stdin.isTTY = true;
+      stdin.isRaw = false;
+      stdin.setRawMode = function setRawMode(mode) {
+        throw new Error('Not a Raw device');
+      };
+
       return stdin;
     });

process.stdout object is actually a Socket object, so columns and rows will not make sense in this case.

@silverwind
Copy link
Contributor Author

Doing this unconditionally worries me a bit. For the isTTY case, I wonder why the code path in tty_wrap.cc that sets it isn't triggered.

@thefourtheye
Copy link
Contributor

The isTTY is set in tty module's ReadStream constructor function. When the new process is created, guessHandleType calls uv_guess_handle with fd as 0 and than returns PIPE, instead of TTY. So, a net.Socket is created and returned. If uv_guess_handle returned TTY, tty.ReadStream would have been invoked, and isTTY and isRaw would be set properly.

@silverwind
Copy link
Contributor Author

Hmm, I don't see the process.stdin getter invoked at all when accessing these properties, so your patch doesn't seem to change anything. I'm guessing we have to check out ttywrap.

@thefourtheye
Copy link
Contributor

@silverwind Actually, I tested the changes and it is working fine. Shall I submit a PR with the code in this issue as a test?

@silverwind
Copy link
Contributor Author

Go ahead, it's good to have a PR so we can run CI anyways.

@thefourtheye
Copy link
Contributor

@silverwind I am not making changes for the columns and rows, as they will be undefined anyway.

@silverwind
Copy link
Contributor Author

I'm primarily after stdin.isTTY, but I wasn't seeing any difference, it looked as if startup.processStdio was never invoked.

thefourtheye added a commit to thefourtheye/io.js that referenced this issue Aug 9, 2015
When a child process is created, the `stdin` will not have `isTTY`,
`isRaw` and `setRawMode` properties. Because, `uv_guess_handle` in
`guessHandleType` call returns `PIPE` for fd 0. So, we create a
`net.Socket` and return. But normally it will return `TTY` and we create
`tty.ReadStream` and return where all those properties are properly set.

This path explicitly sets the above mentioned properties on the returned
socket object.

Fixes: nodejs#2333
PR-URL: nodejs#2336
@brendanashworth
Copy link
Contributor

Mmm #2160?

@silverwind
Copy link
Contributor Author

@sabrehagen is it possible that your script was being piped to or was being called by exec when you observed that issue?

@silverwind
Copy link
Contributor Author

Been thinking about this some more. The docs are pretty clear that these ReadStream properties are only defined when an actual TTY is attached:

When io.js detects that it is being run inside a TTY context, then process.stdin will be a tty.ReadStream

I think I'll just leave it at that. A possible change to introduce these properties would be a breaking change if done right, and I don't think it's worth doing so, it doesn't really solve any issues.

@thefourtheye
Copy link
Contributor

But stdin will be inconsistent when we exec, I am not sure if that is okay

@silverwind
Copy link
Contributor Author

True. I don't understand completely what exec really does yet. When doing process.stdin.pipe(child.stdin) after exec, I don't think isTTY and isRaw do update correctly, but probably should.

@silverwind silverwind reopened this Aug 11, 2015
@thefourtheye
Copy link
Contributor

I believe the same problem should be with spawn as well. I'll check that today

@silverwind
Copy link
Contributor Author

I gave up on doing the rework of your PR because there'd be a tiny change of breakage when someone does process.stdin.isTTY === undefined and it's kind of a pointless breaking change if we intialize these values when stdin isn't actually a ReadStream.

The issue I was trying to solve is this:

var child = exec("iojs child.js", function (err, stdout, stderr) {
  process.stdout.write(stdout);
});
process.stdin.pipe(child.stdin);

After this pipe is made, the child's stdin should upgrade to a ReadStream instance (and have these properties defined). Ideally, this should happen before the child executes its first line of code.

@Trott
Copy link
Member

Trott commented Mar 17, 2016

@silverwind wrote:

Ideally, this should happen before the child executes its first line of code.

The child is a separate node process, right? Is there realistically even a way to guarantee order like that when there are two separate processes?

@59naga
Copy link

59naga commented Mar 28, 2016

FYI
when i encountered this issue, wanted the following requirements:

  • execution of a command that can't be run using spawn.
  • non-blocking (not sync)
  • stdio inheritance

In other words, by executing a shell in the spawn, it has been resolved.

// parent.js
const spawn = require('child_process').spawn;
spawn('NODE_ENV=production node ./child.js', { shell: true, stdio: 'inherit' });
// child.js
const chalk = require('chalk');
process.stdout.write(`${chalk.magenta('child')}\n`);

screen shot 2016-03-29 at 03 26 16

the above doesn't work with node 5.6 or less

@jasnell
Copy link
Member

jasnell commented Jun 7, 2016

I'm wondering if there's really anything to do here other than simply expand the documentation to account for this.

# for free to join this conversation on GitHub. Already have an account? # to comment
Labels
doc Issues and PRs related to the documentations. tty Issues and PRs related to the tty subsystem.
Projects
None yet
Development

Successfully merging a pull request may close this issue.