Ruby 4.0 + net-http 0.8.0 ではより良い open_timeout が使えます
最近書いたパッチです。
Net::HTTP.open の open_timeout オプションを指定することで、コネクションが確立するまでのタイムアウトを設定することができます。Ruby 4.0とnet-http 0.8.0以上の組み合わせではこの実装が変わり、Ruby実装の Timeout.timeout ではなく、ネイティブに実装された TCPSocket.open(open_timeout:) を利用するようになります。ユーザーとしては気にすることはないはずで、勝手に良くなるはずです。
この変更によって、 Timeout.timeout が起動するRubyスレッドが必要なくなり、タイムアウトの正確性もちょっとよくなります。
また、non-main Ractor内でもnet/httpを使ってHTTPリクエストを送れるようになります。個人的にはこちらがモチベーションでした。
require 'net/http'
Ractor.new {
uri = URI('http://example.com/')
http = Net::HTTP.new(uri.host, uri.port)
http.open_timeout = 1 # デフォルト
http.get(uri.path)
}.take # Ruby 4.0 では .value
このコードはRuby 3.4では Ractor::IsolationError (can not access non-shareable objects in constant Timeout::TIMEOUT_THREAD_MUTEX by non-main ractor.) を発生させますが、Ruby 4.0では問題なく動作します。Ractorの中でできることがまたひとつ増えてうれしいですね。HTTPSのためにはopensslの対応がまだまだ必要そうではあります。
このパッチを書くにあたって、しおいさん に TCPSocket.open に open_timeout: オプションを実装していただきました。Ruby 4.0以降でしか動作しないのは、このためにRuby 3.4にはない機能を作ってもらったからです。快く実装していただき & その後も相談に乗っていただき、ありがとうございます。
TCPSocketの既存のオプション resolv_timeout: と connect_timeout: は存在するものの、特にHEv2が有効のときはかなり非自明なタイムアウトの挙動を示すということで、使いやすいAPIとして open_timeout: を整備していただいたのでした。
Timeout.timeoutを使った実装はそもそもDNSのクエリをタイムアウトできないだとか、そうでなくても挙動が予測しづらいAPIなことから、net/httpのタイムアウトを別の方式に変更する試みは 過去にも 何度か なされてきた ようです。
getaddrinfoの改善やHEv2の導入と連動し、net/httpのタイムアウトの挙動も実は徐々に良くなっていたのですが、このたびかなり良いタイムアウトの実現に至ったのではないかと思います。