Skip to content

Commit

Permalink
Merge pull request #398 from local-ch/improved-timeouts
Browse files Browse the repository at this point in the history
Improving timeout behavior and documentation
  • Loading branch information
hanshasselberg committed Nov 3, 2014
2 parents 84aeca2 + 34838de commit 9689e4d
Show file tree
Hide file tree
Showing 3 changed files with 83 additions and 8 deletions.
22 changes: 16 additions & 6 deletions README.md
Expand Up @@ -347,17 +347,27 @@ end

### Timeouts

No exceptions are raised on HTTP timeouts. You can check whether a request timed out with the following methods:
No exceptions are raised on HTTP timeouts. You can check whether a request timed out with the following method:

```ruby
Typhoeus.get("www.example.com").timed_out?
Typhoeus.get("www.example.com", timeout: 1).timed_out?
```

Timed out responses also have their success? method return false.

There are two different timeouts available: [`timeout`](http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTTIMEOUT)
and [`connecttimeout`](http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUT). `timeout` is the
maximum time in seconds that you allow the libcurl transfer operation to take and `connecttimeout` is the maximum
time in seconds that you allow the connection to the server to take. These two are always available, while `timeout_ms` ond
`connecttimeout_ms` accept milliseconds but only an option when curl is build with `c-ares`, it will use `timeout` or `connecttimeout` otherwise.
and [`connecttimeout`](http://curl.haxx.se/libcurl/c/curl_easy_setopt.html#CURLOPTCONNECTTIMEOUT).
`timeout` is the time limit for the entire request in seconds.
`connecttimeout` is the time limit for just the connection phase, again in seconds.

There are two additional more fine grained opptions `timeout_ms` and
`connecttimeout_ms`. These options offer millisecond precision but are not always available (for instance on linux if `nosignal` is not set to true).

When you pass a floating point `timeout` (or `connecttimeout`) Typhoeus will set `timeout_ms` for you if it has not been defined. The actual timeout values passed to curl will always be rounded up.

DNS timeouts of less than one second are not supported unless curl is compiled with an asynchronous resolver.

The default `timeout` is 0 (zero) which means curl never times out during transfer. The default `connecttimeout` is 300 seconds. A `connecttimeout` of 0 will also result in the default `connecttimeout` of 300 seconds.

### Following Redirections

Expand Down
28 changes: 26 additions & 2 deletions lib/typhoeus/easy_factory.rb
Expand Up @@ -64,19 +64,43 @@ def get
private

def sanitize(options)
sanitized = {}
# set nosignal to true by default
# this improves thread safety and timeout behavior
sanitized = {:nosignal => true}
request.options.each do |k,v|
next if [:method, :cache_ttl].include?(k.to_sym)
s = k.to_sym
next if [:method, :cache_ttl].include?(s)
if new_option = renamed_options[k.to_sym]
warn("Deprecated option #{k}. Please use #{new_option} instead.")
sanitized[new_option] = v
# sanitize timeouts
elsif [:timeout_ms, :connecttimeout_ms].include?(s)
if !v.integer?
warn("Value '#{v}' for option '#{k}' must be integer.")
end
sanitized[k] = v.ceil
else
sanitized[k] = v
end
end

sanitize_timeout!(sanitized, :timeout)
sanitize_timeout!(sanitized, :connecttimeout)

sanitized
end

def sanitize_timeout!(options, timeout)
timeout_ms = (timeout.to_s + '_ms').to_sym
if options[timeout] && options[timeout].round != options[timeout]
if !options[timeout_ms]
options[timeout_ms] = (options[timeout]*1000).ceil
end
options[timeout] = options[timeout].ceil
end
options
end

# Sets on_complete callback on easy in order to be able to
# track progress.
#
Expand Down
41 changes: 41 additions & 0 deletions spec/typhoeus/easy_factory_spec.rb
Expand Up @@ -15,6 +15,47 @@
expect(easy_factory.get).to be_a(Ethon::Easy)
end
end

context "timeouts" do
it "sets nosignal to true by default" do
expect(easy_factory.easy).to receive(:http_request).with(anything(), anything(), hash_including(:nosignal => true))
easy_factory.get
end

context "when timeout is not a whole number and timeout_ms is not set" do
let(:options) { {:timeout => 0.1} }
it "ceils timeout and sets timeout_ms" do
expect(easy_factory.easy).to receive(:http_request).with(anything(), anything(), hash_including(:timeout_ms => 100, :timeout => 1))
easy_factory.get
end
end

context "when timeout is not a whole number and timeout_ms is set" do
let(:options) { {:timeout => 0.1, :timeout_ms => 123} }
it "ceils timeout and does not change timeout_ms" do
expect(easy_factory.easy).to receive(:http_request).with(anything(), anything(), hash_including(:timeout_ms => 123, :timeout => 1))
easy_factory.get
end
end

context "when connecttimeout is not a whole number and connecttimeout_ms is not set" do
let(:options) { {:connecttimeout => 0.1} }
it "ceils connecttimeout and sets connecttimeout_ms" do
expect(easy_factory.easy).to receive(:http_request).with(anything(), anything(), hash_including(:connecttimeout_ms => 100, :connecttimeout => 1))
easy_factory.get
end
end

context "when connecttimeout is not a whole number and connecttimeout_ms is set" do
let(:options) { {:connecttimeout => 0.1, :connecttimeout_ms => 123} }
it "ceils connecttimeout and does not change connecttimeout_ms" do
expect(easy_factory.easy).to receive(:http_request).with(anything(), anything(), hash_including(:connecttimeout_ms => 123, :connecttimeout => 1))
easy_factory.get
end
end


end

context "when invalid option" do
let(:options) { {:invalid => 1} }
Expand Down

0 comments on commit 9689e4d

Please sign in to comment.