Rescue Timeout::Error from Redis Gem (Ruby)

2019-04-15 09:15发布

问题:

I need to rescue a Timeout::Error raised from a the Redis library but i'm running into a problem, rescuing that specific class doesn't seem to work.

begin
  Redis.new( { :host => "127.0.0.X" } )
rescue Timeout::Error => ex
end

=> Timeout::Error: Timeout::Error from /Users/me/.rvm/gems/ree-1.8.7-2011.03@gowalla/gems/redis-2.2.0/lib/redis/connection/hiredis.rb:23:in `connect'

When i try to rescue Exception it still doesn't work

begin
  Redis.new( { :host => "127.0.0.X" } )
rescue Exception => ex
end

=> Timeout::Error: Timeout::Error from /Users/me/.rvm/gems/ree-1.8.7-2011.03@gowalla/gems/redis-2.2.0/lib/redis/connection/hiredis.rb:23:in `connect'

If i try to raise the exception manually, i can rescue it but don't know why i can't rescue it when it's called from within the Redis Gem (2.2.0).

begin
  raise Timeout::Error
rescue Timeout::Error => ex
  puts ex 
end

Timeout::Error
=> nil 

Any clue how to rescue this exception?

回答1:

You ran this code in irb, right? The exception you are getting is not actually being raised by Redis.new. It is being raised by the inspect method, which irb calls to show you the value of the expression you just typed.

Just look at the stack trace (I shortened the paths to make it legible):

ruby-1.8.7-p330 :009 >   Redis.new(:host => "google.com")
Timeout::Error: time's up!
    from /.../SystemTimer-1.2.3/lib/system_timer/concurrent_timer_pool.rb:63:in `trigger_next_expired_timer_at'
    from /.../SystemTimer-1.2.3/lib/system_timer/concurrent_timer_pool.rb:68:in `trigger_next_expired_timer'
    from /.../SystemTimer-1.2.3/lib/system_timer.rb:85:in `install_ruby_sigalrm_handler'
    from /..../lib/ruby/1.8/monitor.rb:242:in `synchronize'
    from /.../SystemTimer-1.2.3/lib/system_timer.rb:83:in `install_ruby_sigalrm_handler'
    from /.../redis-2.2.2/lib/redis/connection/ruby.rb:26:in `call'
    from /.../redis-2.2.2/lib/redis/connection/ruby.rb:26:in `initialize'
    from /.../redis-2.2.2/lib/redis/connection/ruby.rb:26:in `new'
    from /.../redis-2.2.2/lib/redis/connection/ruby.rb:26:in `connect'
    from /.../SystemTimer-1.2.3/lib/system_timer.rb:60:in `timeout_after'
    from /.../redis-2.2.2/lib/redis/connection/ruby.rb:115:in `with_timeout'
    from /.../redis-2.2.2/lib/redis/connection/ruby.rb:25:in `connect'
    from /.../redis-2.2.2/lib/redis/client.rb:227:in `establish_connection'
    from /.../redis-2.2.2/lib/redis/client.rb:23:in `connect'
    from /.../redis-2.2.2/lib/redis/client.rb:247:in `ensure_connected'
    from /.../redis-2.2.2/lib/redis/client.rb:137:in `process'
... 2 levels...
    from /.../redis-2.2.2/lib/redis/client.rb:46:in `call'
    from /.../redis-2.2.2/lib/redis.rb:90:in `info'
    from /..../lib/ruby/1.8/monitor.rb:242:in `synchronize'
    from /.../redis-2.2.2/lib/redis.rb:89:in `info'
    from /.../redis-2.2.2/lib/redis.rb:1075:in `inspect'
    from /..../lib/ruby/1.8/monitor.rb:242:in `synchronize'
    from /.../redis-2.2.2/lib/redis.rb:1074:in `inspect'
    from /..../lib/ruby/1.8/irb.rb:310:in `output_value'
    from /..../lib/ruby/1.8/irb.rb:159:in `eval_input'
    from /..../lib/ruby/1.8/irb.rb:271:in `signal_status'
    from /..../lib/ruby/1.8/irb.rb:155:in `eval_input'
    from /..../lib/ruby/1.8/irb.rb:154:in `eval_input'
    from /..../lib/ruby/1.8/irb.rb:71:in `start'
    from /..../lib/ruby/1.8/irb.rb:70:in `catch'
    from /..../lib/ruby/1.8/irb.rb:70:in `start'
    from /..../bin/irb:17

As you can see above, the exception occurs inside inspect, not Redis.new. When you call inspect on a Redis object, instead of just printing out its state it actually does a lot of things. In this case, inspect attempts to connect to the server and throws an exception when that times out. This seems like a very bad design to me and maybe we should file a bug report to the maintainers of the Redis gem.

This leads to some interesting behavior in IRB:

  • Typing Redis.new(:host => "google.com") results in an exception as shown above
  • Typing Redis.new(:host => "google.com"); 'hello' results in '=> "hello"'

If you want to catch this exception, try calling ensure_connected inside your begin/rescue/end block.