[rails/spring] シグナルによるプロセス終了時の終了コードを正しくクライアントに伝播

rails/spring

背景

Springは、Railsアプリケーションの起動時間を短縮するために、アプリケーションをバックグラウンドでプリロードしておくツールです。しかし、子プロセスがクリーンに終了しなかった場合(シグナルで終了した場合など)、Spring経由で実行すると本来の終了コードがマスクされ、常に 0 が返される問題がありました。

これは、Rubyプロセスがクラッシュした際に、実際には異常終了しているにも関わらず、呼び出し元には正常終了として扱われてしまうという深刻な問題です。Issue #676 で報告されていました。

技術的な変更内容

Process::Status の正しい取り扱い

問題の核心は Process::Status#exitstatus の挙動にあります。このメソッドは、プロセスが exit() で正常終了した場合は終了コードを返しますが、シグナルで終了した場合は nil を返します。

変更前:

_, status = Process.wait2 pid
log "#{pid} exited with #{status.exitstatus}"

streams.each(&:close)
client.puts(status.exitstatus)
client.close

このコードでは、status.exitstatusnil の場合、client.puts(nil) が実行され、クライアント側で空文字列として読み取られていました。

変更後:

_, status = Process.wait2 pid
log "#{pid} exited with #{status.exitstatus || status.inspect}"

streams.each(&:close)
client.puts(status.exitstatus || status.to_i)
client.close

status.to_i メソッドは、Process::Status オブジェクトに対して呼び出すと、以下のように動作します:

  • 正常終了の場合: exitstatus の値を返す(例: 0
  • シグナル終了の場合: 128 + シグナル番号 を返す(例: SIGTERM(15)なら 143
  • 停止の場合: 128 + 停止シグナル番号 を返す

これにより、どのような終了方法でも適切な終了コードがクライアントに伝わるようになります。

クライアント側のエラーハンドリング強化

status = application.read
log "got exit status #{status.inspect}"

# Status should always be an integer. If it is empty, something unexpected must have happened to the server.
if status.to_s.strip.empty?
  log "unexpected empty exit status, app crashed?"
  exit 1
end

exit status.to_i

クライアント側では、サーバーから受け取った終了ステータスが空の場合(サーバーが予期せずクラッシュした場合など)を検出し、明示的に終了コード 1 で終了するようになりました。これにより、サーバー側の異常をより確実に検出できます。

テストケースの追加

test "passes exit code from exit and signal" do
  artifacts = app.run("bin/rails runner 'Process.exit(7)'")
  code = artifacts[:status].exitstatus || artifacts[:status].termsig
  assert_equal 7, code, "Expected exit status to be 7, but was #{code}"
end

明示的に終了コード 7 で終了するテストを追加し、その終了コードが正しく伝播されることを確認しています。

影響範囲

この変更により、以下のようなケースで正しい終了コードが返されるようになります:

  • Process.exit(n) で明示的に指定した終了コード
  • セグメンテーションフォルト(SIGSEGV)によるクラッシュ
  • kill コマンドなどによるシグナル送信
  • その他の異常終了

CI/CDパイプラインやスクリプトで Spring を使用している場合、これまで見逃されていたエラーが正しく検出されるようになるため、潜在的な問題の早期発見につながります。

記事メタデータ

Generated by:
Claude Sonnet 4.5 for DiffDaily

この記事はAIによって自動生成されています。内容の正確性については、必ずソースコードやPRを確認してください。

品質レビュー結果

Review Status:
承認済み
Review Count:
1回
Reviewed by:
Gemini 2.5 Pro for DiffDaily

Review Criteria:

ガイドライン準拠 ⚠ WARNING

記事構成とDiffDaily Styleへの準拠状況

記事の構成(Title, Context, Technical Detail)、コードブロック前後の空行、対象読者への適合性といった主要なガイドラインはすべて遵守されています。ただし、GitHubのIssue/PRリンク記法が推奨形式と若干異なっています。

  • 記事構成(Title、Context、Technical Detail)
  • DiffDaily Styleガイド準拠
  • カスタムMarkdown活用
  • 対象読者への適合性
技術的整合性 ✓ PASS

技術的な正確性と表現の適切性

Diff内容との照合、技術用語の正確性、説明の論理性のいずれも問題ありません。特に、Process::Statusの挙動と、status.to_iがなぜ解決策になるのかという解説が技術的に正確で分かりやすいです。

  • 技術用語の正確性
  • コード例の正確性
  • 説明の技術的正確性
PR内容との整合性 ✓ PASS

元のPR情報との一致度

記事内のすべての主張は、PRのタイトル、Diff、関連Issueの内容によって裏付けられており、ハルシネーションは検出されませんでした。PR番号やIssue番号などの固有名詞も正確です。

  • タイトル・説明の一致
  • Diff内容の正確な反映
  • 推測の排除