Spring クライアントがサーバ状態を把握し再試行するよう改善
Spring クライアントは、サーバが起動中もしくは残存した pidfile が無効な状態でも、単なる接続エラーとして失敗していました。本 PR はその2ケースを検知し、boot timeout 相当の待機とサーバ再起動を自動的に行うことで、回復可能なシナリオを自動化します。
背景
Spring クライアントは、サーバに接続しバージョンハンドシェイクを行う際に、サーバがまだアプリケーションのプリロード中であるか、前回のサーバプロセスが死んで pidfile が残っていると Error connecting to Spring server を即座に返していました。これら 2 つの状態はいずれも一時的な問題であり、本来は待機または再起動で解決できるはずです。結果として、ユーザーは不可避なエラーに直面し、手動でサーバを再起動する必要がありました。
この問題を解決するためには、クライアント側でサーバの実際の状態を判断し、適切なリトライや再起動処理を行うロジックが必要です。さらに、リトライは無限ループにならないよう boot timeout を上限として制御することが求められました。
技術的な変更
lib/spring/client/run.rb に ServerReadTimeout 例外クラスを新設し、read_server_line メソッドにタイムアウト引数を導入しました。メソッドは IO.select が nil を返した場合に例外を送出し、呼び出し側で細かくハンドリングできるようしています。
verify_server_version の実装を大幅に改変し、ServerReadTimeout を捕捉した際に waiting_for_server_boot? が真であれば Spring.boot_timeout を使用して再度 read_server_line を試行します。再試行でもタイムアウトが発生すれば reboot_or_raise_connection_error を呼び出し、サーバが起動中でない場合は再起動ロジックへ移行します。
サーバから受信した行が nil だったケースでも同様に reboot_or_raise_connection_error が実行され、サーバが応答しない状態を一元的に処理します。これにより、従来の Error connecting to Spring server が発生する条件が減少し、回復可能なシナリオが自動化されます。
新たに追加された read_server_line(timeout = Spring.connect_timeout) メソッドは、デフォルトで従来の接続タイムアウトを使用しつつ、呼び出し側が明示的に Spring.boot_timeout を指定できるようになっています。コードブロックは以下の通りです。
def read_server_line(timeout = Spring.connect_timeout)
raise ServerReadTimeout if IO.select([server], [], [], timeout).nil?
server.gets
end
テスト側では test/support/acceptance_test.rb に 2 つの受け入れテストを追加し、1 クライアントがサーバ起動中に待機するケースと、古いサーバが死んでいるために再起動が必要になるケースを実証しています。テストは Spring.connect_timeout と Spring.boot_timeout を短縮設定し、BootWaitProbe モジュールで read_server_line の呼び出しをフックして待機経路を検証しています。
module BootWaitProbe
def read_server_line(timeout = Spring.connect_timeout)
if timeout == Spring.boot_timeout && ENV["EXPECT_BOOT_WAIT_PROBE"] == "1"
File.write("#{boot_wait_path}", "1")
end
super
end
end
Spring::Client::Run.prepend(BootWaitProbe)
設計判断
今回の実装は 新しい設定項目を導入せず、既存の Spring.connect_timeout と Spring.boot_timeout を組み合わせてリトライ時間を制御する方針を取りました。これにより、ユーザーが追加設定を覚える必要がなく、既存ドキュメントとの整合性が保たれます。
エラー処理は reboot_or_raise_connection_error に集約し、サーバが起動中か死んでいるかを判定した上で再起動または例外送出を行うことで、ロジックの重複を防ぎました。waiting_for_server_boot? と server_booted? の判定結果に依存するため、将来的な判定ロジック変更にも一箇所だけ修正すれば済む設計になっています。
受け入れテストの追加は、実際の Spring プロセスとクライアント間のタイミングを再現し、boot‑wait パス と 再起動パス の両方が期待通りに動作することを保証します。テストは Shellwords 経由でプロセス管理し、ログとステータスファイルで結果を検証しています。
まとめ
本 PR は Spring クライアントにサーバ状態の認識と限定的リトライを導入し、起動待機中や古いサーバが残っているケースを自動回復させます。既存のタイムアウト設定を活用しつつ、エラーハンドリングを集中化した設計により、ユーザーへの影響を最小限に抑えつつ信頼性を向上させました。