タイムアウトsttyが停止

タイムアウトsttyが停止

次のスクリプトを実行する理由を理解できません。

$ 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.sh10秒かかります(戻り値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 --userwhich(とは異なりtimeout)を使用すると、このコマンドで生成された子プロセスがプロセスグループまたはセッションをエスケープしようとした場合でも、それらをキャッチして終了できます。

おすすめ記事