0

I don't understand why the behavior of executing the following script:

$ cat z.sh
saved_stty=$(stty -g)
echo "saved_stty: ${saved_stty}"
stty "${saved_stty}"
$ ./z.sh
saved_stty: 500:5:bf:8a3b:3:1c:7f:15:4:0:1:0:11:13:1a:0:12:f:17:16:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0
$ echo "${?}"
0

changes after stty is invoked with a timeout:

$ cat z.sh
saved_stty=$(stty -g)
echo "saved_stty: ${saved_stty}"
timeout 10 stty "${saved_stty}"
$ ./z.sh
saved_stty: 500:5:bf:8a3b:3:1c:7f:15:4:0:1:0:11:13:1a:0:12:f:17:16:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0
$ echo "${?}"
124
$ . ./z.sh
saved_stty: 500:5:bf:8a3b:3:1c:7f:15:4:0:1:0:11:13:1a:0:12:f:17:16:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0:0
$ echo "${?}"
0

In the first case, the invokation ./z.sh finishes immediately (with return value 0) while in the second case, the invokation ./z.sh takes 10s (and timeouts with return value 124). However, the invokation . ./z.sh still finishes immediately (with return value 0).

By debugging stty, I observed that the execution hangs at the system call

return INLINE_SYSCALL (ioctl, 3, fd, cmd, &k_termios);

in the function int __tcsetattr (int fd, int optional_actions, const struct termios *termios_p) of glibc/sysdeps/unix/sysv/linux/tcsetattr.c.

I have shown a minimized example of an issue. Now let me make it more realistic:

$ cat z.scala
object Test {
  def main(args: Array[String]): Unit = {
    var j = 0
    val k = 1000
    for (i <- 1 to k) {
      for (i <- 1 to k) {
        j += i
      }
    }
    println(j)
  }
}
$ cat run.sh
timeout 10 scala ./z.scala
echo "${?}"
$ ./run.sh
500500000
124
$ cat z.scala
object Test {
  def main(args: Array[String]): Unit = {
    var j = 0
    val k = 1000000
    for (i <- 1 to k) {
      for (i <- 1 to k) {
        j += i
      }
    }
    println(j)
  }
}
$ cat run.sh
timeout --foreground 10 scala ./z.scala
echo "${?}"
$ ./run.sh
124

The first invokation of scala computes the result immediately but hangs in an internal invokation of stty until timeout. The second invokation of scala is supposed to be killed by timeout but the process executing scala is still running after the script returns. This matches the documentation of timeout --foreground, but I still wonder how to timeout scala properly.

1 Answer 1

1

That happens because timeout runs the command in background, in a separate process group.

When a process is a) attached to the controlling terminal and b) not in the foreground group and tries to change the terminal settings with tcsetattr(), it gets a SIGTTOU signal which stops it.

That's exactly what happened in your example.

GNU timeout has an option for this: --foreground, which you can safely use with simple programs which try to mess with the controlling terminal but do not fork, because then their children won't be killed.

Possible workarounds are to

a) redirect the stdin/stdout/stderr of the programs from somewhere else, in the hope that they won't open /dev/tty explicitly and will leave the controlling terminal alone

b) if they can't be convinced by a), then give them their own pseudo-terminal to run in, which you can do with a program like script(1). Notice that you have to apply the a) treatment to script(1), as it tries to read from stdin (which will get it a SIGTTIN signal) and it tries to make it raw with tcseattr() (which will get it a SIGTTOU signal):

% cat <<'EOT' > sample.sh; chmod +x sample.sh
#!/bin/sh
t=$(stty -g -F /dev/tty)
sleep 1000 &
echo BEFORE; stty -F /dev/tty "$t"; echo AFTER
EOT

% sh -c 'timeout 2 ./sample.sh'
BEFORE
  # hangs for 2 seconds and exits without writing 'AFTER'
%

% sh -c 'timeout 2 script /dev/null </dev/null -qc ./sample.sh'
BEFORE
AFTER
  # and it exits immediately
% pgrep sleep
  # nothing, the child was killed too

Notice that script(1) is not standardized and its syntax is different on other systems than Linux, so you'll have to adapt the example.

c) if you have systemd, use systemd-run -t --user which, (unlike timeout) is able to catch and kill any children spawned by the command, even if they try to escape their process group or session.

1
  • I have shown a minimized example of an issue. I have extended my question to make it more realistic. Sep 4, 2021 at 8:24

You must log in to answer this question.

Not the answer you're looking for? Browse other questions tagged .