時々クラッシュする(実際にはハングする)子プロセスを起動するbashスクリプトがあり、明確な理由はありません(ソースが閉じているため、私ができることはあまりありません)。結果として、私はこのプロセスを一定時間起動し、一定時間経過しても正常に戻らない場合は強制終了できるようにしたいと考えています。
bashを使用してそれを達成する簡単で堅牢な方法はありますか?
PS:この質問がserverfaultまたはスーパーユーザーに適しているかどうか教えてください。
時々クラッシュする(実際にはハングする)子プロセスを起動するbashスクリプトがあり、明確な理由はありません(ソースが閉じているため、私ができることはあまりありません)。結果として、私はこのプロセスを一定時間起動し、一定時間経過しても正常に戻らない場合は強制終了できるようにしたいと考えています。
bashを使用してそれを達成する簡単で堅牢な方法はありますか?
PS:この質問がserverfaultまたはスーパーユーザーに適しているかどうか教えてください。
回答:
(BASH FAQエントリ#68で見られる ように:「コマンドを実行して、N秒後にコマンドを中止(タイムアウト)させるには?」)
何かをダウンロードしてもかまわない場合は、timeout
(sudo apt-get install timeout
)を使用して次のように使用します(ほとんどのシステムにはすでにインストールされていますが、それ以外の場合はsudo apt-get install coreutils
)
timeout 10 ping www.goooooogle.com
何かをダウンロードしたくない場合は、タイムアウトが内部的に行うことを行います:
( cmdpid=$BASHPID; (sleep 10; kill $cmdpid) & exec ping www.goooooogle.com )
より長いbashコードに対してタイムアウトを実行する場合は、2番目のオプションを次のように使用します。
( cmdpid=$BASHPID;
(sleep 10; kill $cmdpid) \
& while ! ping -w 1 www.goooooogle.com
do
echo crap;
done )
cmdpid=$BASHPID
は、呼び出し側シェルのpidではなく、によって開始される(最初の)サブシェルを受け取り()
ます。(sleep
...事は...バックグラウンドで10秒待ってからキラーサブシェルプロセスを立ち上げた後、そのワークロードを実行するために進み、最初のサブシェルを殺すために最初のサブシェル内の第2のサブシェルを呼び出す
timeout
GNU coreutilsの一部であるため、すべてのGNUシステムにすでにインストールされているはずです。
timeout
は現在、coreutilsの一部です。
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) &
または終了コードも取得するには:
# Spawn a child process:
(dosmth) & pid=$!
# in the background, sleep for 10 secs then kill that process
(sleep 10 && kill -9 $pid) & waiter=$!
# wait on our worker process and return the exitcode
exitcode=$(wait $pid && echo $?)
# kill the waiter subshell, if it still runs
kill -9 $waiter 2>/dev/null
# 0 if we killed the waiter, cause that means the process finished before the waiter
finished_gracefully=$?
kill -9
プロセスが最初に処理できることを示す信号を試す前に使用しないでください。
dosmth
2秒で終了し、別のプロセスが古いpidを取得して、新しいpidを強制終了するとどうなりますか?
sleep 999&
t=$!
sleep 10
kill $t
sleep 999
ここ)が、強制されたスリープ(sleep 10
)よりも速く終了することが多い場合はどうなりますか?1分、5分までのチャンスを与えたい場合はどうなりますか?スクリプトにそのようなケースがたくさんあるとどうなるでしょうか:)
私にもこの質問があり、さらに2つのことが非常に役立つことがわかりました。
だから私はコマンドラインでこのようなものを使います(OSX 10.9):
ping www.goooooogle.com & PING_PID=$(pgrep 'ping'); SECONDS=0; while pgrep -q 'ping'; do sleep 0.2; if [ $SECONDS = 10 ]; then kill $PING_PID; fi; done
これはループなので、CPUをクールに保つために「sleep 0.2」を含めました。;-)
(ところで、pingは悪い例です。組み込みの "-t"(タイムアウト)オプションを使用するだけです。)
子のpidを追跡するためのpidファイルがある(または簡単に作成できる)場合、pidファイルのmodtimeをチェックし、必要に応じてプロセスを強制終了/再起動するスクリプトを作成できます。次に、スクリプトをcrontabに配置して、ほぼ必要な期間で実行します。
詳細が必要な場合はお知らせください。それがあなたのニーズに合うように聞こえない場合、新興企業はどうですか?
1つの方法は、サブシェルでプログラムを実行し、名前付きパイプを介してサブシェルと通信することです。 read
コマンドです。このようにして、実行中のプロセスの終了ステータスを確認し、パイプを介して通信することができます。
以下はyes
、3秒後にコマンドをタイムアウトする例です。を使用してプロセスのPIDを取得しますpgrep
(おそらくLinuxでのみ機能します)。また、読み取り用にパイプを開くプロセスは、書き込み用にも開かれるまでハングし、その逆も同様であるという点で、パイプの使用にはいくつかの問題があります。したがって、read
コマンドがハングするのを防ぐために、バックグラウンドサブシェルで読み取り用にパイプを「くさび」で開いています。(つまり、パイプを読み書き可能にするためのフリーズを防ぐ別の方法、つまりread -t 5 <>finished.pipe
Linux以外では機能しない場合もあります。)
rm -f finished.pipe
mkfifo finished.pipe
{ yes >/dev/null; echo finished >finished.pipe ; } &
SUBSHELL=$!
# Get command PID
while : ; do
PID=$( pgrep -P $SUBSHELL yes )
test "$PID" = "" || break
sleep 1
done
# Open pipe for writing
{ exec 4>finished.pipe ; while : ; do sleep 1000; done } &
read -t 3 FINISHED <finished.pipe
if [ "$FINISHED" = finished ] ; then
echo 'Subprocess finished'
else
echo 'Subprocess timed out'
kill $PID
fi
rm finished.pipe
プロセスが既に終了した後にプロセスを強制終了しないようにする試みは次のとおりです。これにより、同じプロセスIDを持つ別のプロセスを強制終了する可能性が低くなります(この種のエラーを完全に回避することはおそらく不可能です)。
run_with_timeout ()
{
t=$1
shift
echo "running \"$*\" with timeout $t"
(
# first, run process in background
(exec sh -c "$*") &
pid=$!
echo $pid
# the timeout shell
(sleep $t ; echo timeout) &
waiter=$!
echo $waiter
# finally, allow process to end naturally
wait $pid
echo $?
) \
| (read pid
read waiter
if test $waiter != timeout ; then
read status
else
status=timeout
fi
# if we timed out, kill the process
if test $status = timeout ; then
kill $pid
exit 99
else
# if the program exited normally, kill the waiting shell
kill $waiter
exit $status
fi
)
}
likeを使用しますrun_with_timeout 3 sleep 10000
。これは実行されますsleep 10000
が、3秒後に終了します。
これは、バックグラウンドタイムアウトプロセスを使用して、遅延後に子プロセスを強制終了する他の回答と同じです。これは、Danの拡張回答(https://stackoverflow.com/a/5161274/1351983)とほぼ同じだと思います。ただし、タイムアウトシェルがすでに終了している場合は、タイムアウトシェルは強制終了されません。
このプログラムが終了した後も、実行中のいくつかの「スリープ」プロセスが残っていますが、無害なはずです。
これは、移植性のread -t
ないシェル機能を使用せず、を使用しないため、他の回答よりも優れたソリューションである可能性がありますpgrep
。
(exec sh -c "$*") &
とsh -c "$*" &
?具体的には、なぜ後者ではなく前者を使用するのですか?
ここに私が提出した3番目の回答があります。これは、シグナル割り込みを処理し、SIGINT
を受信したときにバックグラウンドプロセスをクリーンアップします。これは、トップアンサーで使用されている$BASHPID
and exec
トリックを使用して、プロセスのPID(この場合は呼び出し)を取得します。FIFOを使用して、強制終了とクリーンアップを担当するサブシェルと通信します。(これは私の2番目の回答のパイプに似ていますが、名前付きパイプがあると、シグナルハンドラーもパイプに書き込むことができます。)$$
sh
run_with_timeout ()
{
t=$1 ; shift
trap cleanup 2
F=$$.fifo ; rm -f $F ; mkfifo $F
# first, run main process in background
"$@" & pid=$!
# sleeper process to time out
( sh -c "echo \$\$ >$F ; exec sleep $t" ; echo timeout >$F ) &
read sleeper <$F
# control shell. read from fifo.
# final input is "finished". after that
# we clean up. we can get a timeout or a
# signal first.
( exec 0<$F
while : ; do
read input
case $input in
finished)
test $sleeper != 0 && kill $sleeper
rm -f $F
exit 0
;;
timeout)
test $pid != 0 && kill $pid
sleeper=0
;;
signal)
test $pid != 0 && kill $pid
;;
esac
done
) &
# wait for process to end
wait $pid
status=$?
echo finished >$F
return $status
}
cleanup ()
{
echo signal >$$.fifo
}
私はできる限り競合状態を避けようとしました。ただし、削除できなかったエラーの原因の1つは、プロセスがタイムアウトとほぼ同じ時間で終了した場合です。たとえば、run_with_timeout 2 sleep 2
またはrun_with_timeout 0 sleep 0
。私にとって、後者はエラーを出します:
timeout.sh: line 250: kill: (23248) - No such process
すでに終了しているプロセスを強制終了しようとしているためです。