我不明白为什么执行以下脚本的行为:
$ 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
使用超时调用stty后发生的更改:
$ 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
在第一种情况下,调用./z.sh
立即结束(返回值为0),而在第二种情况下,调用./z.sh
为10s (超时值为124)。但是,调用. ./z.sh
仍然会立即完成(返回值为0)。
通过调试stty,我观察到执行挂起系统调用。
return INLINE_SYSCALL (ioctl, 3, fd, cmd, &k_termios);
在函数int __tcsetattr (int fd, int optional_actions, const struct termios *termios_p)
of glibc/sysdeps/unix/sysv/linux/tcsetattr.c
中。
我已经给出了一个最小化的问题例子。现在让我让它更加现实:
$ 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
scala的第一个调用会立即计算结果,但会挂起stty的内部调用,直到超时。scala的第二次调用应该通过超时来终止,但是执行scala的进程在脚本返回后仍在运行。这与timeout --foreground
的文档相匹配,但我仍然想知道如何正确地超时scala。
发布于 2021-09-03 22:09:20
这是因为timeout
在后台在单独的进程组中运行命令。
当一个进程是( a)附加到控制终端和( b)不在前台组并试图用tcsetattr()
改变终端设置时,它得到一个SIGTTOU
信号来阻止它。
这正是在你的例子中发生的。
GNU timeout
有一个选项:--foreground
,您可以安全地使用这些简单的程序,这些程序试图扰乱控制终端,但是do不是叉,因为这样他们的子程序就不会被杀死。
可能的解决办法是
( a)将程序的stdin/stdout/stderr重定向到其他地方,希望它们不会显式地打开/dev/tty
,而不会让控制终端单独使用。
( b)如果他们不能被a说服,那么给他们自己的伪终端来运行,你可以用script(1)
这样的程序来完成。请注意,您必须将a)处理应用于script(1)
,因为它尝试从stdin (这将获得一个SIGTTIN
信号)读取,并试图使它与tcseattr()
(这将获得SIGTTOU
信号)原始:
% 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
请注意,script(1)是不标准化的,它的语法在其他系统上与Linux不同,因此您必须修改这个示例。
( c)如果您有systemd,那么使用systemd-run -t --user,它(与timeout不同)能够捕获和杀死命令生成的任何子级,即使他们试图转义他们的进程组或会话。
https://unix.stackexchange.com/questions/667543
复制相似问题