public this repo is viewable by everyone
Description: Ruby on Rails
Homepage: http://rubyonrails.org
Clone URL: git://github.com/rails/rails.git
Introduce ActiveResource::Base.timeout. This allows a timeout to be set on 
the internal Net::HTTP instance ARes uses (default is 60 seconds). Setting 
a low timeout allows ARes clients to fail-fast in the event of a 
unresponsive/crashed server, rather than cause cascading failures in your 
application.

Signed-off-by: Michael Koziarski <michael@koziarski.com>
chuyeow (author)
about 1 month ago
NZKoz (committer)
24 days ago
commit  105910429d5873dce677ef32eef5f705e0625d86
tree    f68855ea14f72e6a094f84f660b5421baaa58308
parent  4809dcc1b50330a04ec61dd1fef6cdba9892ac3d
...
166
167
168
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
169
170
171
...
257
258
259
260
 
261
262
263
264
265
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
266
267
268
...
271
272
273
 
274
275
276
...
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
...
277
278
279
 
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
...
306
307
308
309
310
311
312
0
@@ -166,6 +166,26 @@ module ActiveResource
0
   #
0
   # Learn more about Active Resource's validation features in the ActiveResource::Validations documentation.
0
   #
0
+ # === Timeouts
0
+ #
0
+ # Active Resource relies on HTTP to access RESTful APIs and as such is inherently susceptible to slow or
0
+ # unresponsive servers. In such cases, your Active Resource method calls could timeout. You can control the
0
+ # amount of time before Active Resource times out with the +timeout+ variable.
0
+ #
0
+ # class Person < ActiveResource::Base
0
+ # self.site = "http://api.people.com:3000/"
0
+ # self.timeout = 5
0
+ # end
0
+ #
0
+ # This sets the +timeout+ to 5 seconds. You can adjust the timeout to a value suitable for the RESTful API
0
+ # you are accessing. It is recommended to set this to a reasonably low value to allow your Active Resource
0
+ # clients (especially if you are using Active Resource in a Rails application) to fail-fast (see
0
+ # http://en.wikipedia.org/wiki/Fail-fast) rather than cause cascading failures that could incapacitate your
0
+ # server.
0
+ #
0
+ # Internally, Active Resource relies on Ruby's Net::HTTP library to make HTTP requests. Setting +timeout+
0
+ # sets the <tt>read_timeout</tt> of the internal Net::HTTP instance to the same value. The default
0
+ # <tt>read_timeout</tt> is 60 seconds on most Ruby implementations.
0
   class Base
0
     # The logger for diagnosing and tracing Active Resource calls.
0
     cattr_accessor :logger
0
@@ -257,12 +277,27 @@ module ActiveResource
0
         write_inheritable_attribute("format", format)
0
         connection.format = format if site
0
       end
0
-
0
+
0
       # Returns the current format, default is ActiveResource::Formats::XmlFormat
0
       def format # :nodoc:
0
         read_inheritable_attribute("format") || ActiveResource::Formats[:xml]
0
       end
0
 
0
+ # Sets the number of seconds after which requests to the REST API should time out.
0
+ def timeout=(timeout)
0
+ @connection = nil
0
+ @timeout = timeout
0
+ end
0
+
0
+ # Gets tthe number of seconds after which requests to the REST API should time out.
0
+ def timeout
0
+ if defined?(@timeout)
0
+ @timeout
0
+ elsif superclass != Object && superclass.timeout
0
+ superclass.timeout
0
+ end
0
+ end
0
+
0
       # An instance of ActiveResource::Connection that is the base connection to the remote service.
0
       # The +refresh+ parameter toggles whether or not the connection is refreshed at every request
0
       # or not (defaults to <tt>false</tt>).
0
@@ -271,6 +306,7 @@ module ActiveResource
0
           @connection = Connection.new(site, format) if refresh || @connection.nil?
0
           @connection.user = user if user
0
           @connection.password = password if password
0
+ @connection.timeout = timeout if timeout
0
           @connection
0
         else
0
           superclass.connection
...
55
56
57
58
 
59
60
61
...
90
91
92
 
 
 
 
 
93
94
95
...
167
168
169
 
170
171
172
173
174
175
176
 
177
178
179
180
181
 
182
183
184
...
55
56
57
 
58
59
60
61
...
90
91
92
93
94
95
96
97
98
99
100
...
172
173
174
175
176
177
178
179
180
181
 
182
183
184
185
186
 
187
188
189
190
0
@@ -55,7 +55,7 @@ module ActiveResource
0
   # This class is used by ActiveResource::Base to interface with REST
0
   # services.
0
   class Connection
0
- attr_reader :site, :user, :password
0
+ attr_reader :site, :user, :password, :timeout
0
     attr_accessor :format
0
 
0
     class << self
0
@@ -90,6 +90,11 @@ module ActiveResource
0
       @password = password
0
     end
0
 
0
+ # Set the number of seconds after which HTTP requests to the remote service should time out.
0
+ def timeout=(timeout)
0
+ @timeout = timeout
0
+ end
0
+
0
     # Execute a GET request.
0
     # Used to get (find) resources.
0
     def get(path, headers = {})
0
@@ -167,18 +172,19 @@ module ActiveResource
0
         http = Net::HTTP.new(@site.host, @site.port)
0
         http.use_ssl = @site.is_a?(URI::HTTPS)
0
         http.verify_mode = OpenSSL::SSL::VERIFY_NONE if http.use_ssl
0
+ http.read_timeout = @timeout if @timeout # If timeout is not set, the default Net::HTTP timeout (60s) is used.
0
         http
0
       end
0
 
0
       def default_header
0
         @default_header ||= { 'Content-Type' => format.mime_type }
0
       end
0
-
0
+
0
       # Builds headers for request to remote service.
0
       def build_request_headers(headers)
0
         authorization_header.update(default_header).update(headers)
0
       end
0
-
0
+
0
       # Sets authorization header
0
       def authorization_header
0
         (@user || @password ? { 'Authorization' => 'Basic ' + ["#{@user}:#{ @password}"].pack('m').delete("\r\n") } : {})
...
88
89
90
 
 
 
 
 
 
91
92
93
...
108
109
110
 
 
 
 
 
 
 
 
 
 
111
112
113
...
232
233
234
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
235
236
237
...
279
280
281
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
282
283
284
...
88
89
90
91
92
93
94
95
96
97
98
99
...
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
...
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
...
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
0
@@ -88,6 +88,12 @@ class BaseTest < Test::Unit::TestCase
0
     assert_equal('test123', Forum.connection.password)
0
   end
0
 
0
+ def test_should_accept_setting_timeout
0
+ Forum.timeout = 5
0
+ assert_equal(5, Forum.timeout)
0
+ assert_equal(5, Forum.connection.timeout)
0
+ end
0
+
0
   def test_user_variable_can_be_reset
0
     actor = Class.new(ActiveResource::Base)
0
     actor.site = 'http://cinema'
0
@@ -108,6 +114,16 @@ class BaseTest < Test::Unit::TestCase
0
     assert_nil actor.connection.password
0
   end
0
 
0
+ def test_timeout_variable_can_be_reset
0
+ actor = Class.new(ActiveResource::Base)
0
+ actor.site = 'http://cinema'
0
+ assert_nil actor.timeout
0
+ actor.timeout = 5
0
+ actor.timeout = nil
0
+ assert_nil actor.timeout
0
+ assert_nil actor.connection.timeout
0
+ end
0
+
0
   def test_credentials_from_site_are_decoded
0
     actor = Class.new(ActiveResource::Base)
0
     actor.site = 'http://my%40email.com:%31%32%33@cinema'
0
@@ -232,6 +248,40 @@ class BaseTest < Test::Unit::TestCase
0
     assert_equal fruit.password, apple.password, 'subclass did not adopt changes from parent class'
0
   end
0
 
0
+ def test_timeout_reader_uses_superclass_timeout_until_written
0
+ # Superclass is Object so returns nil.
0
+ assert_nil ActiveResource::Base.timeout
0
+ assert_nil Class.new(ActiveResource::Base).timeout
0
+ Person.timeout = 5
0
+
0
+ # Subclass uses superclass timeout.
0
+ actor = Class.new(Person)
0
+ assert_equal Person.timeout, actor.timeout
0
+
0
+ # Changing subclass timeout doesn't change superclass timeout.
0
+ actor.timeout = 10
0
+ assert_not_equal Person.timeout, actor.timeout
0
+
0
+ # Changing superclass timeout doesn't overwrite subclass timeout.
0
+ Person.timeout = 15
0
+ assert_not_equal Person.timeout, actor.timeout
0
+
0
+ # Changing superclass timeout after subclassing changes subclass timeout.
0
+ jester = Class.new(actor)
0
+ actor.timeout = 20
0
+ assert_equal actor.timeout, jester.timeout
0
+
0
+ # Subclasses are always equal to superclass timeout when not overridden.
0
+ fruit = Class.new(ActiveResource::Base)
0
+ apple = Class.new(fruit)
0
+
0
+ fruit.timeout = 25
0
+ assert_equal fruit.timeout, apple.timeout, 'subclass did not adopt changes from parent class'
0
+
0
+ fruit.timeout = 30
0
+ assert_equal fruit.timeout, apple.timeout, 'subclass did not adopt changes from parent class'
0
+ end
0
+
0
   def test_updating_baseclass_site_object_wipes_descendent_cached_connection_objects
0
     # Subclasses are always equal to superclass site when not overridden
0
     fruit = Class.new(ActiveResource::Base)
0
@@ -279,6 +329,22 @@ class BaseTest < Test::Unit::TestCase
0
     assert_not_equal(first_connection, second_connection, 'Connection should be re-created')
0
   end
0
 
0
+ def test_updating_baseclass_timeout_wipes_descendent_cached_connection_objects
0
+ # Subclasses are always equal to superclass timeout when not overridden
0
+ fruit = Class.new(ActiveResource::Base)
0
+ apple = Class.new(fruit)
0
+ fruit.site = 'http://market'
0
+
0
+ fruit.timeout = 5
0
+ assert_equal fruit.connection.timeout, apple.connection.timeout
0
+ first_connection = apple.connection.object_id
0
+
0
+ fruit.timeout = 10
0
+ assert_equal fruit.connection.timeout, apple.connection.timeout
0
+ second_connection = apple.connection.object_id
0
+ assert_not_equal(first_connection, second_connection, 'Connection should be re-created')
0
+ end
0
+
0
   def test_collection_name
0
     assert_equal "people", Person.collection_name
0
   end
...
101
102
103
 
 
 
 
 
104
105
106
...
101
102
103
104
105
106
107
108
109
110
111
0
@@ -101,6 +101,11 @@ class ConnectionTest < Test::Unit::TestCase
0
     assert_equal site, @conn.site
0
   end
0
 
0
+ def test_timeout_accessor
0
+ @conn.timeout = 5
0
+ assert_equal 5, @conn.timeout
0
+ end
0
+
0
   def test_get
0
     matz = @conn.get("/people/1.xml")
0
     assert_equal "Matz", matz["name"]

Comments

    No one has commented yet.