[bootsnap] libc の opendir バグに対するフォールバック処理の実装

rails/bootsnap

背景

bootsnap の Native 実装において、一部の環境で opendir システムコールが NULL を返すにもかかわらず errno が 0 のままという異常な状態が発生していました。POSIX 仕様では opendirNULL を返す場合は必ず errno を設定することが定められていますが、実際には libc のバグと思われる挙動により、Ruby の rb_sys_fail がクラッシュする問題が報告されていました(#516)。

この問題は特定の環境やディレクトリ構造でのみ再現するため、根本原因の特定が困難な状況でした。

実装された変更

エラーハンドリングの強化

C拡張部分で opendir の異常な挙動に対処するため、errno が 0 の場合は EINVAL に設定するガード処理を追加しました。

変更後:

dir = opendir(RSTRING_PTR(abspath));
if (dir == NULL) {
    if (errno == ENOTDIR || errno == ENOENT) {
        return result;
    }

    // BUG: Some users reported a crash here because Ruby's syserr trigger
    // a crash if called with `errno == 0`.
    // The opendir spec is quite clear that if it returns NULL, then `errno` must
    // be set, and yet here we are.
    // So turning no errno into EINVAL, and from there I hope to get to the bottom of things.
    if (errno == 0) {
        errno = EINVAL;
    }

    bs_syserr_fail_path("opendir", errno, abspath);
    return Qundef;
}

さらに、エラーメッセージに失敗したパスを含めるため、新しいヘルパー関数を追加しました。

RBIMPL_ATTR_NORETURN()
static void
bs_syserr_fail_path(const char *func_name, int n, VALUE path)
{
    rb_syserr_fail_str(n, rb_sprintf("%s @ %s", func_name, RSTRING_PTR(path)));
}

RBIMPL_ATTR_NORETURN()
static void
bs_syserr_fail_dir_entry(const char *func_name, int n, VALUE dir, const char *d_name)
{
    rb_syserr_fail_str(n, rb_sprintf("%s @ %s/%s", func_name, RSTRING_PTR(dir), d_name));
}

Pure Ruby 実装へのフォールバック

Native 実装でシステムコールエラーが発生した場合、自動的に Pure Ruby 実装にフォールバックする機能を追加しました。

def native_call(root_path)
  # ... 既存の処理 ...

  all_requirables
rescue SystemCallError => error
  if ENV["BOOTSNAP_DEBUG"]
    raise
  else
    Bootsnap.logger&.call("Unexpected error: #{error.class}: #{error.message}")
    ruby_call(root_path)
  end
end

この実装により、デフォルトではエラーをログに記録した上で Pure Ruby 実装にフォールバックし、ユーザーは処理を継続できるようになりました。問題の詳細なデバッグが必要な場合は BOOTSNAP_DEBUG=1 を設定することで、元のエラーを発生させることができます。

テスト環境での設定

テスト実行時には常に詳細なエラー情報を取得するため、BOOTSNAP_DEBUG をデフォルトで有効化しました。

ENV["BOOTSNAP_DEBUG"] = "1"

技術的意義

この変更により、以下の利点が得られます。

  1. 高い可用性: libc の予期しないバグに遭遇しても、アプリケーションがクラッシュせず動作を継続できる
  2. デバッグ可能性: 問題が発生したディレクトリパスを含む詳細なエラーメッセージにより、根本原因の特定が容易になる
  3. 段階的な問題解決: ユーザーはまず動作を継続し、必要に応じてデバッグモードで詳細な調査を行える

Native 実装のパフォーマンスを維持しつつ、環境依存の問題に対するロバスト性を向上させる実装となっています。

記事メタデータ

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:

ガイドライン準拠 ✓ PASS

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

記事構成(Title, Context, Technical Detail)が明確で、必須要素をすべて満たしています。コードブロック前後の空行やファイル名付きシンタックスハイライト、GitHubリンク記法など、カスタムMarkdown構文が正しく使用されており、可読性が高いです。

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

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

引用されているC言語およびRubyのコードは、PRのDiff内容と完全に一致しています。`opendir`, `errno`, `SystemCallError`などの技術用語も文脈に沿って正確に使用されており、技術的な説明はすべてDiffのコード変更によって裏付けられています。

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

元のPR情報との一致度

記事内の主張はすべてPRのDescription、Diff、関連Issue(#516)の内容に基づいており、ハルシネーションは見られません。PR番号(#522)やIssue番号(#516)も正確です。PRが解決しようとしている問題を正確に要約しています。

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