LoginSignup
9
8

More than 5 years have passed since last update.

Net::HTTPがタイムアウトされない件

Last updated at Posted at 2016-03-21

RestClientのような内部でNet::HTTPを使っているような場合も起こります。

タイムアウトされないコード

pry(main)> require "benchmark"
=> true
pry(main)> uri = URI.parse(NOT_TIMEOUT_URL)
=> #<URI::HTTP http://023sos.com>
pry(main)> time = Benchmark.measure do
pry(main)*   Net::HTTP.start(uri.host, uri.port) do |http|
pry(main)*     http.open_timeout = 1
pry(main)*     http.read_timeout = 1
pry(main)*     http.get(uri.request_uri)
pry(main)*   end
pry(main)* end
=> #<Benchmark::Tms:0x00000002655958 @cstime=0.0, @cutime=0.0, @label="", @real=5.746352325000316, @stime=0.0, @total=0.0, @utime=0.0>
[20] pry(main)> p time.real
5.746352325000316
=> 5.746352325000316

5.746352325000316...タイムアウトを1秒にしているはずですが、6秒弱掛かっているではありませんか。タイムアウトがうまく機能していないようです。

色々と調べた結果、名前解決に時間がかかっている場合は、タイムアウトされないことが発覚。

timeout による割り込みは Thread によって実現されています。 C 言語レベルで実装され、 Ruby のスレッドが割り込めない処理に対して timeout は無力です。 そのようなものは実用レベルでは少ないのですが、 Socket などは DNSの名前解決に時間がかかった場合割り込めません (resolv-replace を使用する必要があります)。 その処理を Ruby で実装しなおすか C 側で Ruby のスレッドを意識してあげる必要があります。

resolv-replaceを使ってみる

pry(main)> require "benchmark"
=> true
pry(main)> require "resolv-replace"
=> true
pry(main)> uri = URI.parse(NOT_TIMEOUT_URL))
=> #<URI::HTTP http://023sos.com>
pry(main)> time = Benchmark.measure do
pry(main)*   timeout(1) do
pry(main)*     Net::HTTP.start(uri.host, uri.port) do |http|
pry(main)*       http.open_timeout = 1
pry(main)*       http.read_timeout = 1
pry(main)*       http.get(uri.request_uri)
pry(main)*     end
pry(main)*   end
pry(main)* end
=> => #<Benchmark::Tms:0x00000002416160 @cstime=0.0, @cutime=0.0, @label="", @real=0.8125761109995437, @stime=0.0, @total=0.0, @utime=0.0>
[20] pry(main)> p time.real
0.8125761109995437
=> 0.8125761109995437

なぜか早くなった影響でタイムアウトされず、、、
再度、同じように実行する。

Timeout::Error: execution expired

無事タイムアウトされました!!

resolv-replaceとは

resolv-replaceを使うと、libcのresolverをRubyで実装しているResolveクラスに置き換えることができます。

resolverとは

リゾルバとは、IPアドレスとドメイン名を結びつけるDNSにおいて、ネームサーバにホスト名を通知してIPアドレスの検索を依頼したり、その逆を依頼したりするクライアント側のプログラム。
アプリケーションソフトがIPアドレスやホスト名を必要とする場合には、通常リゾルバを介して名前解決が行われる。
リゾルバは一つ以上のネームサーバのアドレスを知っており、そのネームサーバに問い合わせを行い、返ってきた答えをアプリケーションに渡す。

なぜ早くなったのか

詳しく調べたわけではないので、憶測です。
Resolve::DNSは初期設定のままでは、/etc/resolv.confもしくはプラットフォーム固有のDNS設定を利用します。
私のlocal環境ではGoogleのDNS(8.8.8.8, 8.8.4.4)を使うようになっており、このDNSを利用したため早くなったと思われます。
そこで、stagingやproductionの環境とlocalの環境とで利用するDNSが変わってしまう可能性があり、注意が必要です。

考察

  • Crawlerを作るときは、DNSを変更したり、タイムアウトができたりするので、resolv-replaceを使うと幸せになれる。
  • 環境の差異によって、DNSが変わってしまう恐れがあるので、resolv-replaceを使うときは、初期設定のまま使うことは避けた方が良いと思われる。

参考

Ruby の Net::HTTP のタイムアウトにハマって、結局 Timeout について調べることになった件
Ruby 2.1.0 リファレンスマニュアル Timeoutモジュール
Ruby 2.1.0 リファレンスマニュアル Resolv::DNSクラス
http://morizyun.github.io/blog/open-uri-timeout-ruby/
[Ruby] 例えば、DNS Resolver をすげかえる
リゾルバ

9
8
0

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
9
8