Setting a timeout for Ruby SOAP clients

By Dan McCallum
June 28, 2007

Last night I was wrestling with a Ruby SOAP client of a long-running web-service. After 60 seconds, the script would exit with the following stack:

      
      /usr/local/lib/ruby/1.8/timeout.rb:54:in `rbuf_fill': execution expired (Timeout::Error)
        from /usr/local/lib/ruby/1.8/timeout.rb:56:in `timeout'
        from /usr/local/lib/ruby/1.8/timeout.rb:76:in `timeout'
        from /usr/local/lib/ruby/1.8/net/protocol.rb:132:in `rbuf_fill'
        from /usr/local/lib/ruby/1.8/net/protocol.rb:116:in `readuntil'
        from /usr/local/lib/ruby/1.8/net/protocol.rb:126:in `readline'
        from /usr/local/lib/ruby/1.8/net/http.rb:2017:in `read_status_line'
        from /usr/local/lib/ruby/1.8/net/http.rb:2006:in `read_new'
        from /usr/local/lib/ruby/1.8/net/http.rb:1047:in `request'
         ... 9 levels...
        from /usr/local/lib/ruby/1.8/soap/rpc/proxy.rb:141:in `call'
        from /usr/local/lib/ruby/1.8/soap/rpc/driver.rb:178:in `call'
        ... snip
      
  

Googling "ruby SOAP timeout" etc yields very little in the way of help. So either everyone is writing those speedy little stock ticker webservice clients I read so much about, or the brain dead should be capable of easily configuring a client timeout. Alas.

After getting a bit of hint from Google Groups and clicking around in Ruby-Doc.org, I found the SOAP::HTTPConfigLoader module. It's set_options method consists of registering a long list of blocks for handling various configuration parameters and it concludes with this:

      
        options.add_hook("connect_timeout") do |key, value|
          client.connect_timeout = value
        end
        options.add_hook("send_timeout") do |key, value|
          client.send_timeout = value
        end
        options.add_hook("receive_timeout") do |key, value|
          client.receive_timeout = value
        end
      
  

I'm still not clear on the relationship between a SOAP::RPC::Driver instance and SOAP::HTTPConfigLoader, and it sure would be nice if a complete set of available configuration parameters were published somewhere outside of the std library source code (or maybe it's out there and I just haven't found it, or maybe I'm failing to appreciate the beauty of all this, um, self-documenting code...). In any event, SOAP::RPC::Driver does have a SOAP::Property member named "options", which can be treated as an associative array. So I give this a try to see if I can set an indefinite timeout:

      
require 'soap/rpc/driver'

client = SOAP::RPC::Driver.new("http://domain/?wsdl", "ServiceName")

client.add_method('method_name', 'arg-name')
client.options["receive_timeout"] = -1

result = client.method_name("some-value")
puts result
    
  

No such luck. Browsing /usr/local/lib/ruby/1.8/timeout.rb, I don't see an obvious way to set an indefinite timeout, and I see from the Timeout::timeout method argument names that i should probably specify my timeout in seconds. So, I change receive_timeout again:

      
client.options["receive_timeout"] = 360
    
  

Success.

So, maybe someone more expert than I with Ruby would have seen the solution faster than I did, or know of a better documentation source, but I did think it was interesting that this problem would have been completely unsolvable without either a debugger or direct access to the std library source code. Is this how Ruby development is supposed to work, i.e. that one needs the source in order to understand a library? I get that this might be consistent with Ruby's affinity for "openness" in all its various manifestations, and sure, I guess I can imagine a techie utopia wherein documentation, configuration and code are indistinguishable. But geez... so much time wasted just to set a timeout....

Your Blogmaster:

dmccallum's picture

Dan McCallum

setting timeout when using factory

jrdavis's picture

I am no expert, but as I read the code, the purpose of the set_options method in HTTPConfigLoader is to add hooks to the options of HTTPStreamHandler so that if one sets a value for one of the "exposed" keys in the hash, the value is propagated into the client (an instance of NetHttpClient)

Also, for what it's worth, if one is using the "factory" form of SOAP driver instead of the form you are using, then the method you discovered does not work. Fortunately, the driver does allow one to get to the stream handler directly, which in turn allows one to get to the client, and one may set its receive_timeout directly.


factory = SOAP::WSDLDriverFactory.new(endpoint_url)
driver = factory.create_rpc_driver
driver.streamhandler.client.receive_timeout = 180 # seconds

The reason is that the options instance variable of the driver object is not the same object as the options instance variable of the HTTPStreamHandler. So setting the driver's options does not invoke the hook that HTTPConfigLoader set.

To obtain an unlimited timeout, it seems to me that one may use either nil or zero.

Jim Davis
Toronto, Canada