Node: process.stdin.setRawMode not working after SIGSTOP

Created on 28 Jan 2018  路  8Comments  路  Source: nodejs/node

  • Version: v9.4.0
  • Platform: Linux 4.9.58-18.55.amzn1.x86_64 #1 SMP Thu Nov 2 04:38:47 UTC 2017 x86_64 x86_64 x86_64 GNU/Linux
  • Subsystem:
process.stdin.setRawMode(true);
process.stdin.on('data', function(data) { 
  key = String.fromCharCode(data[0]);
  if(key == 't') console.log('test');
  if(data[0] == 26) {
    console.log("suspending");
    process.kill(process.pid, 'SIGSTOP'); 
    console.log("resuming");    
  }
  if(data[0] == 3) process.exit();
});
console.log("press t to test, ctrl-z to suspend");

Run this process and press t.
It will immediately print out 'test'.
Then use ctrl-z to put the process into the background by sending it SIGSTOP.
On linux use $ fg to resume it to the foreground.
Press t again but it will not print 'test' as it did before until the return carriage is pressed. setRawMode(true) is no longer being honored.

All 8 comments

  • reproducible in mac too.
  • looks like the device attributes w.r.t termios are modified when the program is suspended, under the cover, not from node.js:

$: ~/support/18416 > cat foo.c

#include <termios.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>

int main() {
  struct termios t1, *t2, t3;
  int fd = open("/dev/pts/0", O_RDWR|O_CLOEXEC);
  dup3(fd, 0, O_CLOEXEC);
  tcgetattr(fd, &t1);
  fprintf(stderr, "original: %d %d %d\n", t3.c_iflag, t3.c_oflag, t3.c_lflag);
  t2 = &t1;
  t2->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
  t2->c_oflag &= ~OPOST;
  t2->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
  t2->c_cflag &= ~(CSIZE | PARENB);
  t2->c_cflag |= CS8;
  tcsetattr(fd, TCSADRAIN,t2);
  while(1) {
    if(getchar() == 26)
      kill(getpid(), SIGSTOP);
    else {
      tcgetattr(fd, &t3);
      fprintf(stderr, "modified: %d %d %d\n\r", t3.c_iflag, t3.c_oflag, t3.c_lflag);
    }
  }
}
$: ~/support/18416 >  ./a.out
original: 0 0 0
//hit some key
modified: 10240 4 18960

[1]+  Stopped                 ./a.out
$: ~/support/18416 >  fg
./a.out
t // hit enter
modified: 11520 5 51739
modified: 11520 5 51739
^C
  • if I use strace the issue is not reproducible - probably because the stdin was not a true tty in the first place, when running as child under strace, instead a psuedo-tty?

  • process.stdin.isRaw is true, not reflecting its true state.

Looks like we will need to re-apply the rawMode when returning from suspension.

trying to see if this one makes any difference:

--- a/deps/uv/src/unix/tty.c
+++ b/deps/uv/src/unix/tty.c
@@ -256,6 +256,7 @@ int uv_tty_set_mode(uv_tty_t* tty, uv_tty_mode_t mode) {
       tmp.c_lflag &= ~(ECHO | ICANON | IEXTEN | ISIG);
       tmp.c_cc[VMIN] = 1;
       tmp.c_cc[VTIME] = 0;
+      tmp.c_cc[VSUSP] = 1;
       break;
     case UV_TTY_MODE_IO:
       uv__tty_make_raw(&tmp);

no.

@gireeshpunathil I think you've already conclusively demonstrated that it's not a node.js issue. I would suggest closing this out.

It might be possible to reinitialize the TTY from a SIGCONT signal handler but that's adding workarounds for third-party bugs.

ok, closing based on @bnoordhuis suggestion, with a record of how this can be circumvented by resurrecting old attributes in a signal handler:

$: ~/support/18416 >  cat foo.c
#include <termios.h>
#include <sys/types.h>
#include <signal.h>
#include <unistd.h>
#include <sys/stat.h>
#include <fcntl.h>
#include <stdio.h>
#include <signal.h>

struct termios t1, *t2, t3;
int fd;
void cont_handler(int s) {
  tcsetattr(fd, TCSADRAIN,t2);
}

int main() {
  fd = open("/dev/pts/0", O_RDWR|O_CLOEXEC);
  dup3(fd, 0, O_CLOEXEC);
  tcgetattr(fd, &t1);
  fprintf(stderr, "original: %d %d %d\n", t3.c_iflag, t3.c_oflag, t3.c_lflag);
  t2 = &t1;
  t2->c_iflag &= ~(IGNBRK | BRKINT | PARMRK | ISTRIP | INLCR | IGNCR | ICRNL | IXON);
  t2->c_oflag &= ~OPOST;
  t2->c_lflag &= ~(ECHO | ECHONL | ICANON | ISIG | IEXTEN);
  t2->c_cflag &= ~(CSIZE | PARENB);
  t2->c_cflag |= CS8;
  tcsetattr(fd, TCSADRAIN,t2);
  signal(SIGCONT, cont_handler);
  while(1) {
    if(getchar() == 26)
      kill(getpid(), SIGSTOP);
    else {
      tcgetattr(fd, &t3);
      fprintf(stderr, "modified: %x %x %x\n\r", t3.c_iflag, t3.c_oflag, t3.c_lflag);
    }
  }
}
$: ~/support/18416 >  ./a.out
original: 0 0 0
modified: 2800 4 4a10
modified: 2800 4 4a10

[1]+  Stopped                 ./a.out
$: ~/support/18416 >  fg
./a.out
modified: 2800 4 4a10
modified: 2800 4 4a10
modified: 2800 4 4a10

So as a workaround in the program, I suggest adding:

process.on('SIGCONT', () => {
  process.stdin.setRawMode(false)
  process.stdin.setRawMode(true)
})

which re-instates the rawMode that was otherwise modified when the process was suspected.

Hope this helps.

Thank you! You guys are so fast and it was not really big problem. I tried doing process.stdin.setRawMode(true) but it didn't help probably because node thought it was already set. I didn't think to set it to false first and then back to true. You guys are smart.

https://github.com/libuv/libuv/issues/1292 looks like it might be why it's necessary to setRawMode(false) before setRawMode(true)

Was this page helpful?
0 / 5 - 0 ratings

Related issues

srl295 picture srl295  路  3Comments

loretoparisi picture loretoparisi  路  3Comments

vsemozhetbyt picture vsemozhetbyt  路  3Comments

Brekmister picture Brekmister  路  3Comments

addaleax picture addaleax  路  3Comments