次のスクリプトを実行する理由を理解できません。
$ 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)、2番目の場合は呼び出しに./z.sh
10秒かかります(戻り値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)
関数内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への2回目の呼び出しはタイムアウトで終了する必要がありますが、scalaを実行するプロセスはスクリプトが返された後も実行され続けます。これはドキュメントと一致しますtimeout --foreground
が、Scalaがタイムアウトを正しく処理する方法を知りたいです。
ベストアンサー1
timeout
これは、コマンドが別々のプロセスグループでバックグラウンドで実行されるために発生します。
a)プロセスが制御端末に接続されており、b)フォアグラウンドグループに存在せず、端末設定の変更を使用しようとすると、それを停止する信号をtcsetattr()
受け取ります。SIGTTOU
これがまさにあなたの例で起こるものです。
GNUには、制御端末を妨害したい簡単なプログラムで安全に使用するtimeout
オプションがあります。--foreground
分割しないでくださいなぜなら、彼らの子供たちは死ぬことはないからです。
考えられる解決策は
ㅏ)プログラムのstdin/stdout/stderrを他の場所からリダイレクトします。/dev/tty
明示的に開かないで、制御端末だけをそのままにしてください。
雨)彼らがa)と確信できない場合は、それらに実行できる疑似端末を提供してください。この端末はstdinから読み取ろうとしているので、script(1)
a)ハンドラーを適用する必要があります。script(1)
信号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 </dev/null -qc ./sample.sh'
BEFORE
AFTER
# and it exits immediately
% pgrep sleep
# nothing, the child was killed too
これはscript(1)
標準化されておらず、Linux以外のシステムではその構文が異なるため、この例を適用する必要があります。
氏)systemdがある場合、systemd-run -t --user
which(とは異なりtimeout
)を使用すると、このコマンドで生成された子プロセスがプロセスグループまたはセッションをエスケープしようとした場合でも、それらをキャッチして終了できます。