[bootsnap] libc の opendir バグに対するフォールバック処理の実装
背景
bootsnap の Native 実装において、一部の環境で opendir システムコールが NULL を返すにもかかわらず errno が 0 のままという異常な状態が発生していました。POSIX 仕様では opendir が NULL を返す場合は必ず 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"
技術的意義
この変更により、以下の利点が得られます。
- 高い可用性: libc の予期しないバグに遭遇しても、アプリケーションがクラッシュせず動作を継続できる
- デバッグ可能性: 問題が発生したディレクトリパスを含む詳細なエラーメッセージにより、根本原因の特定が容易になる
- 段階的な問題解決: ユーザーはまず動作を継続し、必要に応じてデバッグモードで詳細な調査を行える
Native 実装のパフォーマンスを維持しつつ、環境依存の問題に対するロバスト性を向上させる実装となっています。