Description
Version
v21.5.0
Platform
Linux hostname 6.5.0-14-generic #14-Ubuntu SMP PREEMPT_DYNAMIC Tue Nov 14 14:59:49 UTC 2023 x86_64 x86_64 x86_64 GNU/Linux
Subsystem
child_process
What steps will reproduce the bug?
- Place the following in a file called
gnarly.js
#!/usr/bin/env node
const cp = require("child_process");
if (process.env.CHILD) {
process.once("SIGINT", () => {});
process.stdin.resume();
} else {
cp.spawn("./gnarly.js", {
stdio: [process.stdin, "pipe", process.stderr],
env: { CHILD: 1, ...process.env },
});
}
chmod +x gnarly.js
- Run
./gnarly.js
- interrupt it with ctrl-c
- Try to use the up or down arrow keys in your shell after a second or so, and realize your shell is stuck in echo mode
How often does it reproduce? Is there a required condition?
consistently, every time.
What is the expected behavior? Why is that the expected behavior?
No response
What do you see instead?
My shells no longer seem to be seeing input, and instead standard input seems to be stuck in "echo mode".
zsh recovers after I SIGINT a couple more times. nushell outright gets "stuck" and no new input is visible.
Additional information
I have had trouble capturing information about this. Tools like strace
and gdb
seem to take stdin and do things, leading to the problem "fixing itself" when being observed directly. I also was having trouble getting gdb
to follow spawned children to see what was going on there.
The main thing, though, is that I'm not exactly sure what system call or the like could be causing my problem. I think it might be related to the following ioctl
call that was setting ICRNL
.... but I could be totally off base (snippet of those calls). I couldn't really reliably reproduce it directly.
565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|ICRNL|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|ICRNL|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|ICRNL|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0565465 ioctl(0, TCSETSW, {c_iflag=BRKINT|ICRNL|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
EDIT: actually I believe the following to be the specific cause, I was able to finally get strace
to do its thing
The following happens during teardown (on the second SIGINT, since the first one gets absorbed by the process.once
thing).
[pid 697631] ioctl(0, TCGETS,{c_iflag=BRKINT|INLCR|ICRNL|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
[pid 697631] ioctl(0, TCSETS, {c_iflag=BRKINT|ICRNL|IXON|IMAXBEL|IUTF8, c_oflag=NL0|CR0|TAB0|BS0|VT0|FF0|OPOST|ONLCR, c_cflag=B38400|CS8|CREAD, c_lflag=ISIG|ICANON|ECHO|ECHOE|ECHOK|IEXTEN|ECHOCTL|ECHOKE, ...}) = 0
FOR c_iflag
NO LONGER HERE:
{'INLCR'}
Added:
{'IXON'}
----------------------
FOR c_oflag
NO LONGER HERE:
set()
Added:
set()
----------------------
FOR c_cflag
NO LONGER HERE:
set()
Added:
set()
----------------------
FOR c_lflag
NO LONGER HERE:
set()
Added:
{'ICANON', 'ECHO'}
----------------------