世界を動かす技術を、日本語で。

TIL: Bashスクリプトにおけるタイムアウト

概要

  • Bashスクリプト でWebサーバー起動後の 状態確認 の問題点
  • until ループの無限待機リスク
  • timeoutコマンド の利用方法と制約
  • until とtimeoutの組み合わせ時の注意点
  • Bashプロセスや別スクリプト での解決策

BashスクリプトでのWebサーバー起動待機と無限ループ問題

  • Webサーバー起動確認 のためにBashスクリプトで until ループを使用
  • 例:until curl --silent --fail-with-body 10.0.0.1:8080/health; do sleep 1; done
  • サーバーが起動失敗 した場合、ループが 永久に継続 するリスク
  • sleep 1 が永遠に実行される状況発生

timeoutコマンドの活用法

  • timeout はコマンド実行に 制限時間 を設定するユーティリティ
  • 指定時間を超えると SIGTERM シグナルで強制終了
  • シグナルは --signal オプションで変更可能(例:timeout --signal=SIGKILL 1s foo
  • 例:timeout 1s sleep 5 → 1秒後にsleepへSIGTERM送信、終了コード124返却

untilとtimeoutの直接併用の課題

  • 直感的にはtimeout 1m until curl ...; do sleep 1; doneのように書きたくなる
  • しかし timeoutkill可能なコマンド を前提としており、 untilはシェルキーワード のため直接利用不可
  • timeout はシェル組み込みコマンドやキーワードには利用できない制約

解決策:Bashプロセスや外部スクリプトでラップ

  • until ループ全体を bash -c でラップし、timeoutの引数として渡す方法
    • 例:timeout 1m bash -c "until curl --silent --fail-with-body 10.0.0.1:8080/health; do sleep 1; done"
  • または untilループを外部スクリプト (例:until.sh)に分離し、
    • timeout 1m ./until.shのように実行

まとめと注意点

  • timeout はシェル組み込みには直接使えない制約
  • bash -c外部スクリプト でラップすることで 実用的にtimeoutを活用可能
  • 無限ループ防止障害時の自動停止 として有効なテクニック

Hackerたちの意見

リトライロジックが必要な時は、だいたいこんな感じでやってるよ。

for i in {0..60}; do
  true -- "$i" # shelleck suppression if eventually_succeeds; then break; fi
  sleep 1s
done

あんまり優雅じゃないけど、まあまあ正しいかな。次のステップは指数バックオフだね。ちょっとしたコンポーザビリティも残るし。

これ、アプリケーションによってはeventually_succeedsのためにtimeoutが必要になるから注意してね。Bashでは、POSIX/IO/プロセスを扱う時は、ディフェンシブコーディングのプラクティスを使う必要があるよ。何をやっても結果が伴うからね。

君次第だけど、shellcheckがその問題を解決する方法は、

for _ in

のように_を使うことだと思うよ。 リンク: https://github.com/koalaman/shellcheck/wiki/SC2034#intention...

どうやらtimeout(1)はGNU Coreutilsの一部らしい。読んだ後、Bash自体の一部かどうかはちょっと不明だった。

あと、注意してね。timeoutコマンドや引数は、/usr/bin/timeoutとBrewのgtimeoutで違うから(それが「g」プレフィックスの由来)。BSDを使ったことがないから、どうなってるのかは知らないんだ。

直接untilと一緒にtimeoutを使えないのは残念だね。 untilキーワードはPOSIX.2シェル仕様の一部で、タイムアウト機能は含まれていないんだ。bashで実装することはできるけど、他のシェルにはポータブルじゃない(Debian dashが主な懸念)。だから、別のユーティリティとして実装されているんだ。仕様を見たいなら、下の「The until loop」を検索してみて。 https://pubs.opengroup.org/onlinepubs/9699919799/utilities/V...

あまり知られていないお気に入りのトリックは、straceの障害注入を使っていろんなsyscallの失敗をテストすること。例えば、

$ strace -e trace=clone -e fault=clone:error=EAGAIN random

リンク: https://medium.com/@manav503/using-strace-to-perform-fault-i...

これはすごい!もっと早く知っていればよかったな。失敗の分岐をテストできないのを知って、こういう関数をよくスタブアウトしてたけど、できるだけ狭い範囲に留めるようにしてたんだ。ありがとう!

これは素晴らしい!Windowsに同じようなものがあるか知ってる人いる?

新しいKubernetesのセットアップでコマンドタイムアウトを追加したばかりだよ。このawait-cmd.sh / await-http.sh / await-tcp.shのPOSIXシェルスクリプト実装は成熟してて、いくつかのシナリオではかなり便利だよ。 リンク: https://github.com/vegardit/await.sh

昔は、

timeout 1800 mplayer show.mp4 ; sudo pm-suspend

を使ってた。子供たちが小さい時に、30分間手動で監視せずにショーを見せるための貧乏人の親のコントロールとしてね。便利なコマンドだよ。

それは多分、最もよく説明されたユースケースだね!

Hacker Newsで議論の続きを見る